Ищем быструю универсальную библиотеку для работы с графическими файлами, разбираемся с Google benchmark

2_2twxajpekzwruuxeno7ssg3l4.jpeg

В наше время, когда нейронные сети бороздят просторы Big Data, а искусственный интеллект раздумывает, выгодно ли ему получать зарплату за свою работу в Bitcoin, доставшаяся мне задача поиска самой быстрой открытой кросс-платформенной библиотеки для загрузки, сохранения и перекодирования графических файлов выглядела настоящим анахронизмом. Но на самом деле эта задача актуальна как никогда — для всех технологий компьютерного зрения и машинного обучения гигабайты картинок надо обязательно загрузить, а иногда и сохранить промежуточные данные в виде изображений. Так что сделать это самым быстрым способом очень желательно. В этой статье мы найдем искомую библиотеку, а, главное, разберемся с очень полезным продуктом, сильно упрощающим подобные и многие другие задачи — Google Benchmark.
Итак, точная формулировка задачи гласит: в приложении загружаются, то есть декодируются в память файлы форматов jpeg и tiff с глубиной цвета 24 и 8 бит, а также 32-битные bmp. Размер изображений варьируется от крошечных (32×32 пикселя) до больших, с разрешением 15K. В процессе работы файлы модифицируются, после чего их требуется сохранять на диск в заданных форматах. И делать это должна кросс-платформенная библиотека с открытым кодом, обладающая максимальной производительностью на современных процессорах Intel с поддержкой векторных инструкций AVX2. Желательна также поддержка библиотекой формата сжатых текстур DirectX DXT1. За точку отсчета производительности берется Windows Imaging Component — стандартный фреймворк для работы с изображениями в Windows, то есть требуется найти библиотеку, работающую на равных или быстрее, чем WIC.

Но самое главное требование — решение нужно вот прямо сейчас, а лучше вчера.

Знакомьтесь, библиотеки для работы с bmp, tiff, jpeg


Решение начинается с очевидного и несложного, хотя и не очень быстрого шага — тщательного изучения Wikileaks github, stackoverflow и прочего google в поисках подходящих кандидатов на роль искомой библиотеки. Таковых оказалось немного:

  • FreeImage. Оболочка над известными библиотеками LibJPEG, LibPNG, LibTIFF. Поддержка DXT1 присутствует посредством плагина. Недостаток — качество сохранения jpeg в API задается слишком дискретно — 100, 75,50 и 25%. Для изменения этого параметра придется разбираться и править код. Проект живой и развивающийся — последяя версия 3.18.0 выпущена 31 июля 2018. Сборка под Windows тривиальна, все компоненты строятся автоматически.
  • Cimg Представляет собой заголовочный С++ файл-обертку над древним артефактом пакетом ImageMagick. Пакет требует отдельной сборки-установки, возможно также прямое его использование, минуя Cimg. Обладает массой возможностей по работе с изображениями: фильтры, преобразования, определение морфологии и т.п. Поддерживает HDR, не поддерживает DXT1.
  • DevIL (Developer’s Image Library). Очень простая библиотека с С интерфейсом в стиле OpenGL. Содержит оболочку над LibJPEG, LibPNG, LibTIFF, но также имеет обширный встроенный функционал, дополнительно поддерживает массу форматов изображений, в том числе DXT1. Для сборки использует CMake. Большинство зависимостей, в том числе и LibJPEG, LibPNG, LibTIFF не входят в состав DevIL и должны быть самостоятельно загружены и собраны отдельно. Последнее обновление DevIL, касающееся системы сборки, датировано 01.2017, а предыдущее — вообще случилось в 2014 году, так что в случае возможных проблем с библиотекой — возможны проблемы с их решением.
  • OpenImageIO. Позиционируется как инструмент разработчика профессионального софта для работы с изображениями. Поддерживает в форме плагинов работу с многочисленными экзотическими форматами фото и даже видео. Сборка для Windows требует предкомпилированных Boost и Qt 4. Готовой собранной версии для тестирования нет.
  • Boost GIL (Generic Image Library) Boost и этим все сказано. Хотя, не все. Эта библиотека также содержит оболочку над LibJPEG, LibPNG и LibTIFF.
  • SDL_image 2.0 Используется вместе с библиотекой SDL и, вы будете смеяться, но также содержит оболочку над LibJPEG, LibPNG и LibTIFF.


Все найденные библиотеки были собраны под Windows с использованием максимального уровня оптимизации компилятора Visual Studio и ключом /arch: AVX2.

То же самое относится и к библиотекам LibJPEG, LibPNG и LibTIFF, для ускорения работы взятым из свежего пакета библиотеки OpenCV.

Знакомьтесь, Google Benchmark


Следующий шаг решения также очевиден — создание бенчмарка для сравнения производительности найденных библиотек, а несложным и быстрым его делает использование широко известной в узких кругах библиотеки для микробенчмаркинга Google Benchmark.
Google Benchmark умеет достаточно точно измерять производительность кусков кода, вставленных вами в тело цикла C++11.

static void BM_foo1(benchmark::State& state) {
//Этот кусок кода не измеряется
Init_your_code();
for (auto _ : state){
//А этот - измеряется
    your_code_to_benchmark();
}


в функциях, зарегистрированных в качестве бенчмарка

// Регистрируем функцию выше в качестве бенчмарка
BENCHMARK(BM_foo1);


И запускать их:

BENCHMARK_MAIN();


После чего выдавать отчет в заданном формате — консольный вывод, json, csv.

Отчет будет содержать данные о системе исполнения (процессор, конфигурация кэш-памяти), общее глобальное время работы каждой из измеряемых функций, а также время, которое они занимают процессор. Эти времена в общем случае отличаются — в первое, например, входит задержка на чтение\запись, а второе для многопоточных бенчмарков складывается из времени работы всех ядер.

Последний выводимый Google benchmark параметр — количество выполненных итераций функции, необходимое для статистически корректного точного измерения времени ее работы. Система выбирает его самостоятельно, автоматически, осуществляя предварительные измерения.

Что такое «точное измерение» времени работы? На эту тему можно писать диссертации, но в данном случае достаточно сказать, что:

  • по умолчанию измерение идет в процессорных клок-тиках, то есть, теоретический порядок точности именно такой. Выдача результата по умолчанию — в наносекундах;
  • результаты во всех виденных мной тестах очень стабильны от запуска к запуску;
  • по моим агентурным данным Google benchmark используют и полностью доверяют его результатам разработчики софта бортовых компьютеров одного крупнейшего мирового автомобильного концерна. Так что поверим и мы.


Единственный момент, на который стоит обратить внимание: Google benchmark не обеспечивает «чистку» кэш-памяти между запусками итераций бенчмарка. Об этом при необходимости вам следует позаботиться самостоятельно.

Зато Google benchmark может много всего другого:

  • посчитать асимптотическую сложность алгоритма (О);
  • корректно работать с многопоточными бенчмарками, измеряя их продолжительность не в процессорных тиках, а в режиме «реальное время» (wall clock);
  • использовать свою собственную функцию «ручного» измерения времени, что может оказаться полезным, например, при измерениях работы на GPU;
  • по заданному телу измеряемой функции автоматически сгенерировать бенчмарки с разными наборами аргументов;
  • показывать среднее значение, медиану и стандартное отклонение при многократных запусках бенчмарка;
  • Задавать собственные счетчики и метки, которые будут отражены в отчете Google benchmark.


Google benchmark загружается из репозитория на github, собирается для соответствующей платформы с использованием Cmake (для Windows доступна сборка Visual Studio), получившаяся библиотека линкуется к вашему проекту (в случае Windows кроме этого потребуется линковка с библиотекой shlwapi), в ваш код добавляется заголовочный файл benchmark.h, после чего все работает, как описано выше.

Если не работает, то единственное место, помимо уже указанного сайта, где можно получить хоть какую-нибудь информацию и помощь по Google benchmark — это специализированный форум по продукту.

В нашем случае все заработало без проблем. После общения с заказчиками были определены 4 бенчмарка, представляющие собой загрузку и сохранение под другим именем:

  • 8-битного jpeg файла с разрешением 15k
  • 24-битного jpeg файла с разрешением 15k
  • 24-битного tiff файла с разрешением 15k
  • 32-битного bmp файла с разрешением 32×32


Знакомьтесь, результаты


Изначально планировалось, что в тестировании-сравнении с Windows Imaging Component (WIC) примут участие все найденные библиотеки, т.е., FreeImage, Cimg, DevIL, OpenImageIO, Boost GIL и SDL_image 2.0. Но три последние библиотеки, зависящие от таких «монстров» как Boost и SDL, заказчики попросили оставить в запасе на крайний случай, если нужная библиотека не найдется среди первых трех. И, к счастью, она нашлась. Хотя и не сразу.

Ниже приведен сгенерированный Google benchmark отчет, из которого видно, что:

  • FreeImage полностью с разгромным счетом проигрывает WIC во всех тестах, так что его можно больше не рассматривать.
  • Cimg проигрывает WIC вчистую везде, кроме загрузки tiff, где он слегка (меньше чем на 5%) быстрее. Увы, его также придется вычеркнуть. Причем, это относится и к прямому использованию пакета ImageMagick


Остается библиотека DevIL. Она показывает отличные результаты в случаях загрузки bmp и tiff (в 3 и 2.8 раза соответственно превосходит WIC!), черно-белого jpeg (в 1.75x лучше WIC), но немного тормозит на загрузке обычного 24-битного jpeg — делает это аж на 3% медленнее WIC.

08/15/18 11:15:44
Running c:\WIC\WIC_test\Release\WIC_test.exe
Run on (8 X 4008 MHz CPU s)
CPU Caches:
L1 Data 32K (x4)
L1 Instruction 32K (x4)
L2 Unified 262K (x4)
L3 Unified 8388K (x1)
Benchmark Time CPU Iterations
BM_WIC8jpeg 72 ms 70 ms 11
BM_cimg8jpeg 562 ms 52 ms 10
BM_FreeImage8jpeg 147 ms 144 ms 5
BM_devIL8jpeg 41 ms 41 ms 17
BM_WIC24jpeg 266 ms 260 ms 3
BM_cimg24jpeg 656 ms 128 ms 6
BM_FreeImage24jpeg 594 ms 594 ms 1
BM_devIL24jpeg 276 ms 276 ms 3
BM_WIC24tiff 844 ms 844 ms 1
BM_cimg24tiff 808 ms 131 ms 5
BM_FreeImage24tiff 953 ms 938 ms 1
BM_devIL24tiff 305 ms 305 ms 2
BM_WIC32 3 ms 3 ms 236
BM_cimg32 71 ms 7 ms 90
BM_FreeImage32 6 ms 5 ms 112
BM_devIL32 1 ms 1 ms 747

Конечно, на этом этапе можно было бы забраковать и DevIL, но тут в кадре появляется еще одна библиотека — Libjpeg-turbo.

Ее выход можно смело встречать аплодисментами — Libjpeg-turbo — это кросс-платформенная библиотека, полностью реализующая функциональность (API) libjpeg и добавляющая к нему собственную функциональность (например, работу с 32-битными буферами). При этом, для x86 архитектуры Libjpeg-turbo активно использует векторные инструкции (SSE2, AVX2) и по утверждению ее создателей, превосходит по скорости libjpeg в 2–6 раз (!)

Поэтому следующий шаг — это сборка DevIL c Libjpeg-turbo вместо libjpeg. Libjpeg-turbo с помощью CMake без проблем собирается Visual Studio, после чего почти сразу (с заменой единственного #define, определяющего версию libjpeg в заголовочном файле DevIL) начинает работать в составе DevIL.

В результате отчет Google benchmark выглядят так:

Benchmark Time CPU Iterations
BM_WIC8jpeg 72 ms 68 ms 9
BM_cimg8jpeg 565 ms 39 ms 10
BM_FreeImage8jpeg 148 ms 141 ms 5
BM_devIL8jpeg 31 ms 31 ms 24
BM_WIC24jpeg 269 ms 266 ms 2
BM_cimg24jpeg 675 ms 131 ms 5
BM_FreeImage24jpeg 604 ms 594 ms 1
BM_devIL24jpeg 149 ms 150 ms 5
BM_WIC24tiff 833 ms 828 ms 1
BM_cimg24tiff 785 ms 138 ms 5
BM_FreeImage24tiff 943 ms 938 ms 1
BM_devIL24tiff 318 ms 320 ms 2
BM_WIC32 4 ms 3 ms 236
BM_cimg32 74 ms 8 ms 56
BM_FreeImage32 6 ms 5 ms 100
BM_devIL32 1 ms 1 ms 747

Конечно же, улучшения производительности работы с jpeg даже в два раза в сравнении с libjpeg здесь не видно, но так и должно быть — ведь превосходство в скорости относится только к кодированию\декодированию jpeg, а тест включает накладные расходы на чтение\запись файла.

Зато видно, что в среднем DevIL работает быстрее WIC в случае 8-бит jpeg в 2.3 раза, 24-бит jpeg в 1.8 раз, 24-бит tiff — в 2.7 раз, 32-бит bmp — 3.5 раз.

Задача решена. На решение было полностью потрачено три летних предотпускных рабочих дня. Конечно, если бы их было чуть больше, возможно, что нашлась бы библиотека с еще более впечатляющими результатами, а если значительно больше, то, возможно, я бы написала искомую библиотеку сама.

Но даже то, что есть — впечатляет. Поэтому, если вы ищете быструю и легкую во всех смыслах кросс-платформенную библиотеку для работы с графическими файлами, то обратите внимание на DevIL, а если вам нужно быстро и качественно произвести сравнительные измерения кода, то к вашим услугам — Google benchmark.

© Habrahabr.ru