вторник, 25 июня 2024 г.

[prog.c++] Любопытное на тему производительности самодельного проверяльщика UTF-8

Довелось недавно столкнуться с задачей проверки корректности UTF-8 представления. И в рамках этой задачи вышел вот на это замечательное описание с примерами двух реализаций: Flexible and Economical UTF-8 Decoder.

Очень мне понравились оба тамошних решения (для простоты буду называть их "решение от 2009-го" и "решение от 2010-го" годов, по датам в копирайтах). Просто и лаконично. Да еще и гибко, можно затачивать хоть под проверку, хоть про декодирование с восстановлением после ошибок.

Поскольку у нас в RESTinio есть что-то аналогичное, но гораздо более объемное по числу строк, то подумалось, что можно было бы заменить наш многострочный utf8_checker на более компактный.

Но прежде чем сделать это попробовал провести простейшие сравнения производительности. И вот тут-то меня ждал сюрприз...

На основном рабочем Windows-ноутбуке с i7-8550U и VisualStudio 2022 (17.10.3) примитивный бенчмарк показывает следующие результаты:

***    restinio: 1240136us ***
*** decode_2009: 1784796us ***
*** decode_2010: 1774765us ***

Компиляция выполнялась так: cl -EHsc -O2 -DNDEBUG -utf-8 -std:c++20

На этом же Windows-ноутбуке под GCC-13.2.0 (из MinGW-w64) получаются следующие числа:

***    restinio: 435519us ***
*** decode_2009: 977052us ***
*** decode_2010: 566962us ***

Компиляция выполнялась так: g++ -O2 -std=c++20

На резервном Linux-ноутбуке с i7-6600U и GCC-13.1 этот же бенчмарк дает следующие числа:

***    restinio: 915891us ***  
*** decode_2009: 873742us ***  
*** decode_2010: 809853us ***

Но еще интереснее оказывается с GCC-11 на том же Linux-ноутбуке:

***    restinio: 941621us ***  
*** decode_2009: 1857220us ***  
*** decode_2010: 2047872us ***

Т.е., похоже, в GCC-13 оптимизатор серьезно прокачали, отсюда и гораздо лучшие результаты для решений от 2009-го и 2010-го годов под GCC-13.

Но намного больше пищи для размышлений дал clang-18:

***    restinio: 2108439us ***  
*** decode_2009: 1775339us ***  
*** decode_2010: 2012727us ***

Т.е. оптимизатор в clang-е с решениями 2009-го и 2010-го годов справился на уровне GCC-11, но вот наш код из RESTinio он настолько же сильно, как GCC-13, оптимизировать не смог.

Ну и для полноты картины результаты clang-16 с того же Linux-ноутбука:

***    restinio: 2379565us ***  
*** decode_2009: 3222073us ***  
*** decode_2010: 1839321us ***

Сразу скажу, что не являюсь специалистом по низкоуровневым оптимизации (да и не любитель я этого занятия). Так что разбираться с тем, что и как оптимизируется, можно ли что-то еще ускорить или нет, не стал.

Меня удивило другое: насколько разные результаты можно получить на одной и той же платформе с разными версиями одного и того же компилятора (не говоря уже о разных компиляторах).

Если взять специфику наших открытых библиотек, которые распространяются в исходниках, а затем компилируются на неизвестно каких платформах неизвестно какими компиляторами, то насколько уместно заниматься низкоуровневыми оптимизациями? Типа отшлифуешь все до последней микросекунды на Windows с VC++, а на Linux-е и 11-ом GCC, напротив, получишь просадку производительности.

PS. Я понимаю, что это негодный бенчмарк. Всего один набор тестовых данных. Весь код в одном файле, что не есть хорошо, если разнести функции по разным .cpp-файлам результаты могут отличаться, например, когда каждая функция декодирования помещается в отдельный C++ файл, то результы с GCC-13 под Linux-ом на i7-6600U меняются в пользу реализации от RESTinio:

***      restinio: 612732us ***
***   decode_2010: 820765us ***  
***   decode_2009: 855889us ***  

Но для осознания глубины кроличьей норы, имхо, сойдет даже такое примитивное сравнение 🙂

5 комментариев:

Stanislav Mischenko комментирует...

В моей практике были случаи, когда оптимизацию приходилось отключать, потому что с оптимизацией программа могла падать. Или, вот например, "загадка дыры" - std::replace не делал замену символов в одном случае из двадцати.

eao197 комментирует...

> В моей практике были случаи, когда оптимизацию приходилось отключать, потому что с оптимизацией программа могла падать.

Если исключить баги компилятора, то это запросто могло быть проявление UB в программе.

Я, кстати, всегда предпочитаю работать сразу в release режиме, с оптимизацией, без колупания в debug-mode. Гораздо меньше сюрпризов потом.

sv комментирует...

Смутило отсутствие флагов векторизации. Возможно, какойто компилятор их автоматом приписывает, если не указано.

eao197 комментирует...

@sv

Там вроде как ключик -O2 всей этой магией должен заведовать.

sv комментирует...

Мне кажется, что нет, хотя могу ошибаться. Лучше всеже попробовать явно передать