All streams
Search
Write a publication
Pull to refresh
8
0
Send message

Мне удалось выжать почти 2 раза (1.8), но это с минимальным размером массива (4096) и с двумя внешними циклами, один просто крутит 2000000 итераций, другой выбирает минимальное время из 20 попыток. Clang 14 / gcc 12.
В реальных условиях наверное чаще будет 5%, чем 2 раза.

Вы же там ниже пишете, что в память упирается - ну правильно, 40 мб.
Автор статьи специально на небольшом размере тестировал, чтобы помещалось в самый быстрый кэш. Можно гонять тест коротких данных много раз.

Получается, в Skylake уже 3 модуля для векторного сложения (Int Vect ALU), так что Clang где-то и прав.
Я брал информацию по вычислительным модулям у Агнера Фога, может он намеренно упоминал только "настоящие" модули с умножением и FMA:
https://www.agner.org/optimize/blog/read.php?i=838

Clang не согласен, он делает ещё развёртку SIMD-кода.
https://gcc.godbolt.org/z/3M8W81rMv
Можно добавить к статье, что сказанное про множественные скалярные арифметические модули справедливо и для SIMD, у Intel сейчас 2 * 256 бит, у AMD 4 * 128 бит. Получается, развёртка Clang 4 * 256 избыточна, ну вот такая у него "широкая душа", любит развернуть с настройками по умолчанию. Может, в расчёте на будущие процессоры.

Не на том материале рекламируете :)

Naked to the bone

Надо было уточнить - мой опыт относится к Си/Дельфи, Go толком не знаю. Сейчас попытался переписать функцию на Go:
https://gcc.godbolt.org/z/ernMd4qE3
Без проверок диапазонов ассемблер похож, кстати, на Дельфи - векторизации нет, но в целом достаточно чистый код.
И так или иначе он работает гораздо быстрее ввода-вывода png. Я даже проверил:
Загрузка 24-битного png через GDI+: 22 мс
Извлечение канала на Дельфи (имитируем Go): 0.76 мс
Извлечение канала на Си: 0.19 мс
Отсюда и вывод, что не должно копирование байтиков заметно тормозить... не знаю, может автору тоже отключить проверки глобально директивой B-? Или это плохой тон?

В Си простые циклы векторизуются автоматически, интринсики вспоминать не надо.
https://gcc.godbolt.org/z/a9ecW4hx4
Для SIMD конечно лучше, если в исходнике 4 байта на пиксель, меньше команд понадобится (или менее "тяжёлых").

Но по моему опыту, копирование с извлечением синего канала не должно сильно влиять. В основном должно упираться в распаковку/упаковку png, потому что png вообще медленный. Как я слышал, zlib-овский inflate/deflate слабо (или вообще никак) ускоряется через SIMD. У вас получилось 20% на выборе оптимизированной библиотеки - ну вот примерно так, да, и это мало. Качественные библиотеки jpeg ускоряют в разы.

Поэтому главное - грамотное распараллеливание упаковки/распаковки. И у вас хороший результат, я не ожидал, что можно делать чтение 1000 картинок и упаковку одной огромной за 0.23 c.

Вон у Oxygene LLVM правильно прикручен:
https://talk.remobjects.com/t/some-interesting-performance-comparision-island-vs-delphi/24596
Хотя тест скорее неудачный - практического смысла в вычислении на этапе компиляции нет, но он показывает, что оптимизация в LLVM работает.
А по Дельфи/Линукс свежей информации нет, придётся видимо самому тестировать.

Нашёл тесты скорости Линуксового кода - действительно в 2 раза медленнее, правда это 2017 год:
https://helloacm.com/integer-performance-comparisons-of-delphi-win32-win64-and-linux64-for-singlemultithreading-counting-prime-number/

По чатам аж половина - ну ОК, возможно я просто не в курсе, планшеты с Андроидом как-то прошли мимо.
Сейчас больше Линукс интересен, но для Линукса с GUI нужно ещё стороннее дополнение, и качество кодогенерации под вопросом. Все компиляторы кроме виндовых работают через LLVM, который в теории может хорошо оптимизировать, но я слышал, что в Дельфи LLVM работает с перманентным -O0, то есть без оптимизации. Хотя может быть эти слухи уже устарели.

А вот преимуществ и возможностей очень много. В особенности у кроссплатформенного фреймворка FMX

Интересно было бы устроить опрос, сколько процентов дельфистов используют FMX.
А то субьективно у меня тоскливое ощущение, что все эти роскошества нафиг никому не нужны.
Новички всё равно на 100% уверены, что Дельфи устарел - хоть с FMX, хоть без. Старые дельфисты поддерживают уже написанный под VCL код и не хотят вдохновляться новыми, будем уж откровенны, свистоперделками типа "уникальной системы стилей", ради которых весь этот VCL-код нужно переписывать.
Ошибка Embarcadero в том, что они сделали ставку на новичков. Если бы выкинули свистоперделки и обеспечили максимальную совместимость с VCL, тогда от FMX было бы больше пользы.
А так получается, что даже пресловутую кроссплатформенность удобнее прикрутить через Freepascal.

Ага, спасибо.
Я особо вьедливо не сравнивал картинки, потому что в обоих случаях это искусственно привнесённый шум (а не например lossless против lossy-сжатия).

Изначально собиралось под Линукс, в исходном архиве есть бенчмарки, но похоже я по незнанию попортил кроссплатформеность.
Сейчас заменил getch на getchar, immintrin.h перенёс внутрь vExt_proc.
Для экзотических платформ нужно задать vExt_proc 0 и tracerProc tracer3, чтобы собрать автовекторизованную версию вместо векторных расширений, которые местами используют интринсики.

У меня основной код был написан давно, в 2017-2018. Всё никак не мог статью дописать, хотелось именно статью, а не просто выложить на форуме. Дотянул до того, что форума уж нет...
И я помню, что например Clang 5 справлялся с векторизацией. А, ну есть же godbolt, можно точно сказать:
https://gcc.godbolt.org/z/ra5MTnfzb
Clang 3.3 и gcc 5.1 нужны для векторизации, 2013-2015 годы.
Но действительно, неустойчиво оно тогда работало, вот этот вариант - уже Clang 3.9 и gcc 4.7
https://gcc.godbolt.org/z/s3bPEzsrr
В том и смысл статьи, показать, что современные компиляторы менее капризны и при грамотном тюнинге кода многое делают сами.

Это конкретно дельфийский компилятор так развивается - по стандартам x64 положено считать плавающую точку на SSE, поэтому её, так и быть, сделали на SSE. А x86? Да ну, чот страшно трогать, поломаем ещё... оставляем FPU. Отсюда и 2 раза.
У FPC, насколько помню, таких заморочек нет, там x86 компилятор может использовать SSE. Хотя наверное, его нужно принудительно включить.

Версия Irfan и сейчас стоит 4.10. Много раз пытался посвежее поставить, но тогда только у этой версии была killer-фича: почти мгновенно (а не через несколько секунд, и после запуска приложения) по нажатию Enter в Проводнике открыть файл на весь экран. Ну и тут же полистать без задержек остальное содержимое папки.

Сочетание быстрого запуска с быстрым листанием кстати мало у кого есть, я даже свой вьюер писал специально для этого, хотя на публику так и не зарелизил, а сейчас уже лень исправлять известные глюки.
Ирфан по-моему не делает предзагрузку следующей, поэтому 16-мп фотки листает всё-таки с небольшой задержкой. В версии 4.10 к тому же не использовалась libjpeg-turbo (появилась с 4.30), там должно быть совсем грустно. Но если картинки мелкие, тогда конечно без разницы.

Фрагмент с пещерами напомнил это:
https://gamedev.ru/projects/forum/?id=258554
Там качество где-то даже и получше, нет квадратов при сильном приближении. Освещение более продвинутое.

Квадраты, понятно, зависят от метода рендера. Если это рейкастинг, то есть для каждого пикселя ищем пересечение луча из камеры с воксельным миром, то сгладить в теории не проблема... той же линейной интерполяцией, если попали лучом между вокселями, считаем среднее по 8 соседним. По сути это выборка из 3D-текстуры с фильтрацией.
Но возможно в движках со сложными структурами данных типа SVO считается, что брать 8 соседних вокселей слишком дорого, они могут быть в разных ветках дерева и т.п.

Подозреваю, что подобных трюков много в статьях по старым псевдо-3D играм, наверняка в Doom 1,2 именно так углы и хранились. И таблицы для тригонометрии, разумеется.

С тестом:
https://gcc.godbolt.org/z/9n53e9588
Квадрат 2*2 оказался медленнее, видимо непоследовательная запись в память всё портила, поэтому переделал на 2 соседних пикселя по горизонтали, теперь в 2 раза быстрее. А простое дописывание vectorize(assume_safety) к циклу почти ничего не даёт (от 0 до 15%, но чаще 0).
Также в варианте с 2*2 я ошибся и не использовал y_pixel_stride, uv_row_stride, uv_pixel_stride - из-за этого и пропали vpinsrb. Все эти параметры дают гибкость, но с ними приходится брать по байтику, компилятор не может быть уверен, что данные лежат последовательно. Впрочем, по факту простыня vpinsrb не делает хуже, ускоряют именно 2 пикселя за итерацию.
Хотя 2 раза - тоже далеко не предел мечтаний, но больше не хочется тратить на это времени.

Действительно как-то очень простенько.
Фактически всё ограничивается знакомством с некими магическими заклинаниями для ускорения кода, "но всемогущий маг лишь на бумаге я" - 18% это очень мало для успешной векторизации.
Я попытался восстановить полный код примера, правда на x86, c ARM/Neon я не знаком:
https://gcc.godbolt.org/z/99Yfd7Y9E
Видно, что векторизует, но масса вот этих vpinsrb, выдёргиваний по 1 байтику - это явно неэффективно.
Буферы U,V имеют в 2 раза меньшее разрешение - логично считать за одну итерацию квадрат 2*2 пикселя, чтобы не читать одни те же значения U,V по 4 раза:
https://gcc.godbolt.org/z/T68hdvTcs
Вроде бы стало лучше, во всяком случае махинаций с байтиками сходу не вижу. Но надо тестировать.
Также я заменил прагму assume_safety на модификатор __restrict для параметров, действует аналогично, но совместимо с gcc.

Ещё следует подумать, можно ли избавиться от плавающей точки с сохранением приемлемой точности, это уберёт лишние команды конверсии и может улучшить векторизацию (если обойтись int16, то их больше влезет в вектор, чем float32).

В общем, надо дописывать свою статью про высокоуровневую векторизацию (у меня на другом примере, но не суть).

Information

Rating
Does not participate
Registered
Activity