В наше время, когда нейронные сети бороздят просторы 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
Решение начинается с очевидного и несложного, хотя и не очень быстрого шага – тщательного изучения
- 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 файла с разрешением 32x32
Знакомьтесь, результаты
Изначально планировалось, что в тестировании-сравнении с 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 |
|||
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 |
Ее выход можно смело встречать аплодисментами — 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 |
Зато видно, что в среднем DevIL работает быстрее WIC в случае 8-бит jpeg в 2.3 раза, 24-бит jpeg в 1.8 раз, 24-бит tiff – в 2.7 раз, 32-бит bmp — 3.5 раз.
Задача решена. На решение было полностью потрачено три летних предотпускных рабочих дня. Конечно, если бы их было чуть больше, возможно, что нашлась бы библиотека с еще более впечатляющими результатами, а если значительно больше, то, возможно, я бы написала искомую библиотеку сама.
Но даже то, что есть – впечатляет. Поэтому, если вы ищете быструю и легкую во всех смыслах кросс-платформенную библиотеку для работы с графическими файлами, то обратите внимание на DevIL, а если вам нужно быстро и качественно произвести сравнительные измерения кода, то к вашим услугам – Google benchmark.
Комментарии (14)
Nomad1
04.10.2018 13:25+1В целом, сравнение разных оберток над одинаковыми libPNG/libJPEG/libTIFF должно было показать почти одинаковый результат. Если это не так, то либо дело в накладных расходах (читай — ошибках реализации), либо в ошибках тестирования. Вам обязательно надо было попробовать напрямую замерить скорость самих этих библиотек, без оберток. Отдельно в TIF файле может быть сжатие RLE, JPEG и LZW (+ чуть более редкие варианты), поэтому libTIFF использует libJPEG и в идеальном варианте скорость открытия TIFF-JPEG должна совпасть со скоростью чистой libJPEG.
Так же замерять скорость чтения BMP файла размером 32х32 откровенно несерьезно — после прогрева кеша его чтение должно быть менее 1мс для абсолютно любой реализации и если это не так, то вы что-то делаете не так.
P.S. Я же надеюсь, вы в курсе, что кодирование в DXT1 отличается в разных реализациях в разы по качеству и скорости? Простенький кодер есть и в ImageMagik, который вы почему-то обозвали древним артефактом, хотя он развивается не в пример лучше того же DevIL. Просто, его задачей никогда не была производительность или удобство встраивания, это универсальный комбайн для всех возможных и невозможных операций с картинками. А в целом, сжатие в DXT1 может быть существенно оптимизировано и разбито на потоки, что хорошо сделали ребята из Unity в своей версии crunch.vikky13 Автор
04.10.2018 13:47-1Это удивительно, но от вашего комментария есть польза. Я увидела, что забыла упомянуть в тексте, что Tiff по условиям-не сжатый. Спасибо!
Nomad1
04.10.2018 14:06+1Это удивительно, но от вашей статьи тоже есть польза — сравнение разных библиотек весьма полезно. Но все перечёркивается очень странными бенчмарками.
FreeImage считывает в память 4220 байт картинки за 5мс? Даже для 1000 итераций это нереально долго, скорость памяти исчисляется в тысячах мегабайт в секунду. Вызов jpeg_start_decompress/jpeg_read_scanlines/jpeg_finish_decompress занимает разное время в разных обертках вокруг одной библиотеки? Почему?
В конце-концов, раз речь шла о кросс-платформенности, где результаты других платформ?vikky13 Автор
04.10.2018 14:22-1Хороший вопрос, простой ответ — значит, кроме считывания (и записи, которая также входит в тест) происходит что-то еще. Что именно — в данном случае неважно. Предпоследний абзац исходного текста, однако. Другие платформы — любопытно, да. Но мы сравниваем с WIC, которого там (пока) нет
lieff
04.10.2018 15:14Давно уже пользуюсь github.com/nothings/stb
Интересно было бы сравнить и его. Так же кроме ImageMagiсk есть еще GraphicsMagick, чья цель как раз была оптимизация — его тоже стоит проверить.
Serge78rus
04.10.2018 15:41Но три последние библиотеки, зависящие от таких «монстров» как Boost и SDL, заказчики попросили оставить в запасе на крайний случай, если нужная библиотека не найдется среди первых трех.
Данное утверждение касательно Boost GIL противоречит фразе из туториала:GIL consists of header files only and does not require any libraries to link against. It does not require Boost to be built and including boost/gil.hpp will be sufficient for most projects.
Nomad1
04.10.2018 16:23А что такое 8-бит JPEG?
Serge78rus
04.10.2018 16:28Очевидно — монохромный (8 бит на пиксель)
Nomad1
04.10.2018 16:34Честно говоря, я думал, там только YCbCr сжатие бывает. Есть модификация только для Y (luminance) компоненты?
Serge78rus
04.10.2018 17:00+1Если верить Википедии, то стандарт JPEG не обязывает использовать пространство YCbCr
Nomad1
04.10.2018 17:39Да, весьма интересно. По какой-то причине не сталкивался с ним раньше — декодеры спокойно дают на выходе 24-битное изображение, не думая об оригинале. А с энкодерами я мало сталкивался по разной причине. Да и инфрмации на эту тему весьма мало.
Imagemagick пишет в такой формат, если ему принудительно это указать. Что интересно, при этом параметр quality не работает вообще, PSNR остается неизменным (51.1808 в моем случае), но это не Lossless сжатие, где-то еще есть внутренние преобразования.
P.S. В Википедии как раз ошибка, формат JFIF подразумевает и Y, и YCbCr:
The RGB components calculated by linear conversion from YCbCr shall not be gamma corrected (gamma = 1.0). If only one component is used, that component shall be Y.
DjOnline
Добавить бы в сравнение Pillow из соседней темы habr.com/post/301576
vikky13 Автор
Да, я ждала, что в комментариях еще библиотек напредлагают. Может, кто-нибудь когда-нибудь за это и возьмется. Задача — очень несложная, студенческая :). Плюс интересно не только и не сколько загрузка-сохранение, но и фильтрация, конвертация цвета и другие операции (хотя там наверняка победит Intel IPP)