[.NET, C++, Обработка изображений, C#] Сравниваем производительность C# и C++ в задачах обработки изображений

Автор Сообщение
news_bot ®

Стаж: 6 лет 4 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
11-Дек-2020 14:31

Бытует мнение, что C# не место в вычислительных задачах, и мнение это вполне обоснованное: JIT-компилятор вынужден компилировать и оптимизировать код на лету в процессе выполнения программы с минимальными задержками, у него попросту нет возможности потратить больше вычислительных ресурсов, чтобы сгенерить более эффективный код, в отличие от компилятора C++, которые может потратить на это дело минуты и даже часы.Однако, в последние годы эффективность JIT-компилятора заметно возросла, да и в сам фреймворк завезли ряд полезных фишек, например, интринсики.И вот стало мне интересно: а можно ли в 2020 году, используя .NET 5.0, написать код, который бы не сильно уступал по производительности C++? Оказалось, что можно.МотивацияЯ занимаюсь разработкой алгоритмов обработки изображений, причём на достаточно низком уровне. То есть это не жонглирование кирпичиками в Python, а именно разработка чего-то нового и, желательно, производительного. Код на Python работает непозволительно долго, тогда как использование C++ приводит к снижению скорости разработки. Оптимальный баланс между продуктивностью и производительностью для подобных задач достигается при использовании C# и Java. В подтверждение моих слов - проект Fiji.Раньше для прототипирования я использовал C#, а готовые алгоритмы, которым критична производительность, переписывал на C++, пихал в либу и дёргал либу из C#. Но в этом случае страдала переносимость, да и отлаживать код было не очень удобно.Но это было давно, с тех пор .NET шагнул далеко вперёд, и мне стало интересно, могу ли я отказаться от нативной библиотеки на C++ и перейти полностью на C#?СценарийСравнивать же языки я буду на примере базовых методов обработки изображений: сумма изображений, поворот, свёртка, медианная фильтрация. Именно подобные методы чаще всего приходится писать на C++. Особенно критично время работы свёртки. Для каждого из методов, кроме медианной фильтрации, было сделано по три реализации на C# и C++:
  • Наивная реализация с использованием методов типа GetPixel(x, y) и SetPixel(x, y, value);
  • Оптимизированная реализация с использованием указателей и работы с ними на низком уровне;
  • Реализация с использованием интринсков (AVX).
В случае медианной фильтрации использовались библиотечные функции (Array.Sort, std::sort), поэтому это было, фактически, сравнение реализаций этих функции, а не пользовательского кода. В перспективе имеет смысл подумать об использовании сортировочных сетей.Также, чтобы уравнять языки в возможностях, я в C# использовал unmanaged память и обращался к пикселям без каких-либо проверок на выход за границы. А то как-то нечестно получается, что C++ использует UB для достижения высокой производительности, а C# - нет.Реализация методов выложена на Github, смысла постить сюда портянки кода я не вижу, просто приведу пример кода на C#:Сумма изображений
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Sum_ThisProperty(NativeImage<float> img1, NativeImage<float> img2, NativeImage<float> res)
{
    for (var j = 0; j < res.Height; j++)
    for (var i = 0; i < res.Width; i++)
        res[i, j] = img1[i, j] + img2[i, j];
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Sum_Optimized(NativeImage<float> img1, NativeImage<float> img2, NativeImage<float> res)
{
    var w = res.Width;
    for (var j = 0; j < res.Height; j++)
    {
        var p1 = img1.PixelAddr(0, j);
        var p2 = img2.PixelAddr(0, j);
        var r = res.PixelAddr(0, j);
        for (var i = 0; i < w; i++)
            r[i] = p1[i] + p2[i];
    }
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Sum_Avx(NativeImage<float> img1, NativeImage<float> img2, NativeImage<float> res)
{
    var w8 = res.Width / 8 * 8;
    for (var j = 0; j < res.Height; j++)
    {
        var p1 = img1.PixelAddr(0, j);
        var p2 = img2.PixelAddr(0, j);
        var r = res.PixelAddr(0, j);доступна
        for (var i = 0; i < w8; i += 8)
        {
            Avx.StoreAligned(r, Avx.Add(Avx.LoadAlignedVector256(p1), Avx.LoadAlignedVector256(p2)));
            p1 += 8;
            p2 += 8;
            r += 8;
        }
        for (var i = w8; i < res.Width; i++)
            *r++ = *p1++ + *p2++;
    }
}
РезультатыПерейдём к результатам. В ячейках таблицы указано время работы (1/10 перцентиль) тестируемых методов в микросекундах для изображений размером 256x256 в градациях серого с типом пикселя float 32 bit.dotnet build -c Releaseg++ 10.2.0 -O0g++ 10.2.0 -O1g++ 10.2.0 -O2g++ 10.2.0 -O3clang 11.0.0 -O2clang 11.0.0 -O3Sum (naive)115.8757.6124.436.2619.5120.1419.81Sum (opt)40.69255.636.0724.4819.6020.1119.81Sum (avx)21.1560.4120.0020.1820.3720.2320.20Rotate (naive)90.29500.387.1536.0114.4914.0414.16Rotate (opt)34.99237.135.1134.1714.5514.1014.27Rotate (avx)14.8351.0414.1414.2514.3714.2214.72Median 3x341632666029301607250823012330Median 5x5115501009082405554587056106051Median 7x723540244701754013640126201292013510Convolve 7x7 (naive)55193090032403694277530472761Convolve 7x7 (opt)29131178027592628275424342262Convolve 7x7 (avx)709.23759729.8669.8684.2643.8638.3Convolve 7x7 (avx*)505.62984523.4511.5507.8443.2443.3Примечание: Convolve 7x7 (avx*) - это свёртка без специальной обработки граничных значений, то есть случай, когда результирующее изображение уменьшается на размер ядра свёртки.Тестирование проводилось на процессоре Core i7-2600K @ 4.0 GHz.Из таблицы можно сделать следующие наблюдения:
  • Скорость работы векторизованного кода (avx), написанного на C#, практически не отличается от аналогичного кода, написанного на C++. Ура, теперь на C# можно прогать математику!
  • Производительность небезопасных низкоуровневых методов в C# тоже достаточно неплоха, и C# сильно проигрывает только там, где компилятор C++ смог применить автовекторизацию.
  • А вот скорость работы наивных реализаций в C# оставляет желать лучшего и проигрывает C++ от 2 до 6 раз. Но для прототипирования это и не важно.
ВыводыДа, на C# можно писать вычислительный код, имеющий паритет по производительности с C++. Но для этого придётся прибегнуть к ручными оптимизациям в коде: то, что компилятор C++ делает в автоматическом режиме, в C# нужно делать самому. Поэтому если у вас нет привязки к C#, то пишите дальше на C++.P.S. В .NET есть одна киллер-фича - это возможность кодогенерации в рантайме. Если пайплайн обработки изображений заранее неизвестен (например, задаётся пользователем), то в C++ придётся собирать его из кирпичиков и, возможно, даже использовать виртуальные функции, тогда как в C# можно добиться большей производительности, просто сгенерив метод.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_.net, #_c++, #_obrabotka_izobrazhenij (Обработка изображений), #_c#, #_c#, #_c++, #_.net, #_obrabotka_izobrazhenij (обработка изображений), #_proizvoditelnost (производительность), #_.net, #_c++, #_obrabotka_izobrazhenij (
Обработка изображений
)
, #_c#
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 07-Июл 08:27
Часовой пояс: UTC + 5