Если понимать, как работает векторизация, то можно "наивный" код довольно легко дотюнить до векторизуемого состояния. Получается короче интринсиков и совместимо с разными системами команд. https://gcc.godbolt.org/z/jbrqGKxnx (хвост проигнорирован для простоты)
Gcc всё сводит к вызову memset (call memset@PLT), видимо у неё внутри SIMD есть. Можно Clang смотреть, там SIMD в явном виде (строки 72-74): https://gcc.godbolt.org/z/PPPz6xv6s
А это справедливо для каких именно чисел: single, double или extended?
Single и Double. Extended в SSE вообще нет, так что в x64 Extended = Double.
Для Single желательно использовать {$EXCESSPRECISION OFF}, иначе расчёты пытается делать в Double и тормозит из-за постоянных преобразований.
Каких-то чудес ждать не стоит, это скалярный SSE без векторизации, ускорение заметно в основном из-за неэффективности старого FPU-кодогенератора (и в целом FPU на современном железе).
не вытягивает Паскаль сложные алгоритмы работы со звуком.
Если у вас расчёты в плавающей точке, то справедливости ради, в современных Дельфях в x64 стало получше, т.к. перешли с FPU на SSE2. Можно рассчитывать на ускорение раза в 2. Но конечно, у С/C++ прогресс компилятора за то же время гораздо больше (автовекторизация и пр.). По той простой причине, что в них вкладывается куда больше сил и денег, чем в непопулярные Паскали.
Да, я заметил, но при желании легко исправить на полную сортировку, нужно переделать функцию Sort(__m128i a[9]). Там сейчас 19 сравнений. Пишут, что для полной сортировки 9 элементов нужно 25, схема есть: https://www.sciencedirect.com/science/article/pii/S0022000015001397 Как раз преимущество подхода в том, что легко переделать на разные сортировочные сети, не придумывая каждый раз схему перемещения элементов в xmm-регистрах.
Ещё в медианном фильтре картинок этот алгоритм применяется: https://habr.com/ru/articles/204682/ там кстати код попроще выглядит, без перетасовок в xmm-регистрах. Используется векторизация в направлении "по массиву" (пикселей) - алгоритм абсолютно аналогичен скалярному, только вместо одного значения работаем с пачками по 16-32 элементов. Поэтому получается всего 6 интринсиков, можно даже и без них, у меня где-то был автовекторизованный вариант. Но да, это подходит для пикселей, а у вас большой пачки из сортируемых фрагментов может и не быть в наличии.
Новые (другие) типы. Также, использовать новую математику, интринсики и т.д.
Какие конкретно? Про SSE я знаю и объяснил, почему это не сработало.
И речь шла о том, что некоторые ускоряют код вообще без изменений. Именно это я и называю "прогресс в компиляторе". Либо сильно ускоряют при умеренных изменениях (затачивание под векторизацию). Единственное, что в Дельфи можно придумать с умеренными изменениями, это распараллеливание циклов. Но если очень захотеть, то можно их параллелить и в D5, хотя это и неудобно без анонимок. Инлайн работает криво и довольно часто замедляет вместо ускорения. И кардтрейсер у меня тоже замедлил.
Компилятор Делфи не застрял в 2000-х, вы заблуждаетесь.
У меня есть проект, который собирается разными версиями Дельфи начиная с D5/1999, недавно протестировал его на D12/2023. И какая же разница в скорости исполняемого кода за 24 года? Никакой! Ну может 1%, на уровне погрешности. Потому что там в основном целочисленные расчёты, и единственное заметное улучшение компилятора (плавающая точка на SSE в x64) не влияет. Так вот и получается, что для моих целей прогресса в компиляторе нет, и он застрял даже можно сказать в 90-х. Андроиды и прочие платформы не особо интересны, разве что Линукс... впрочем, по сообщениям там ещё хуже со скоростью, потому что это криво (с отключенной оптимизацией) прикрученный LLVM. Кто-то может проверить кардтрейсер на Дельфи/Линуксе?
На Си у меня нет проекта в точности аналогичного дельфийскому, но тот же кардтрейсер на gcc версий 4.9 и 12 (между ними 8 лет) показывает на разных ф-ях ускорение 10-20%, а иногда 2+ раза, если новая версия векторизовала, а старая нет. С компилятором 1999 года разница должна быть ещё больше хотя бы из-за поддерживаемого набора инструкций.
Ну что касается "написать правильно", то например C++ можно относительно небольшими правками разогнать в разы, я про это целую статью писал: https://habr.com/ru/articles/685228/ Дельфи от этих правок ускоряется гораздо меньше, потому что векторизацию компилятор не умеет совсем. Хотите SIMD - только хардкор, только ассемблерные простыни вручную.
включая C#, который "всего" в 2 раза медленнее нативного Delphi
Навскидку на этом: https://github.com/Mark-Kovalyov/CardRaytracerBenchmark получаю С# x64 - 9.4, Delphi x64 - 16.8 секунды (однопоток, консольный вывод в Дельфи отключен). Меньше - лучше, то есть С# быстрее почти в 2 раза. Почему? Ну, JIT C# основан вероятно на C++, а кодогенерация в плюсовых компиляторах со временем улучшается. У Дельфи она застряла на уровне 2000-х по причине неустроенности (перепродажи) и нехватки ресурсов на разработку. Я понимаю, почему вы держитесь за написанные и отлаженные млн. строк, просто слегка просвещаю насчёт современного соотношения сил.
Notepad++ даёт небольшую задержку при запуске. Да, едва заметную, может полсекунды, но разница с AkelPad чувствуется. Дело вкуса, но мне хотелось бы мгновенной реакции от контекстного вьюера всякой txt-мелочи.
Тоже когда-то пользовался Bred, потом перешёл на AkelPad - всё то же самое + подсветка, 600 кб. Сейчас проверил новые версии - уже и автодополнение есть, и сворачивание кода при макс. размере 4.5 мб. Но это плагины, при желании можно пользоваться как блокнотом.
64 Гб/c у Pci-Ex 4 - это суммарная в обе стороны, в одну сторону 32 Гб/c. По моим тестам реальная скорость в оптимальном для OpenCL варианте (pinned memory) около 75% от максимальной, 23-24 Гб/c на GF3080. Возможно, серией асинхронных запросов с разными картинками можно выжать больше, у меня гонялась одна.
Тесты с обработкой я тоже делал, и получалось, что единичные простые операции (напр. gauss blur) всё-таки нет смысла гонять в видеокарту. Чем сложнее, тем лучше: AHD debayer - умеренное ускорение раза в два, нейросети - да, самое то :) Или переносить на GPU весь конвейер из простых операций. При этом отлаживать и поддерживать GPU-код сложнее.
"реализация GetEvensCount без if всё-таки чуть-чуть медленнее, чем с if'ами"
Хотя это и несколько оффтоп, но попиарю в очередной раз компилятор Clang: https://gcc.godbolt.org/z/WoMbrx8zx SIMD, развёртка, считаем 32 числа за итерацию - эта версия уж точно не медленнее. Значит, в общем случае избавляться от if выгоднее.
Только я вставлял в сишный проект, поэтому переправил всё с плюсов на чистый Си. Но вряд ли это влияет. И я тоже ожидал, что будет мухлевать с итерациями, но нет, время явно зависит от Cycles.
код
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
unsigned long sum_array(unsigned long* array, unsigned long size)
{
unsigned long sum = 0;
for (unsigned long i = 0; i < size; ++i)
sum += array[i];
return sum;
}
int main()
{
const unsigned long n = 4096;
const Cycles = 2000000;
const Cycles2 = 20;
unsigned long array[n];
srand(42);
for (unsigned long i = 0; i < n; ++i)
array[i] = rand();
LARGE_INTEGER frequency;
LARGE_INTEGER t1, t2, t3, t4;
QueryPerformanceFrequency(&frequency);
unsigned long s = 0;
double minTime = 100000000;
for (int m = 0; m < Cycles2; m++) {
QueryPerformanceCounter(&t1);
for (int i = 0; i < Cycles; i++) {
s = sum_array(array, n);
}
QueryPerformanceCounter(&t2);
double elapsedTime=(double)(t2.QuadPart-t1.QuadPart)/frequency.QuadPart;
if (elapsedTime < minTime) minTime = elapsedTime;
}
printf("sum: %lu\n", s);
printf("microseconds: %.5f seconds\n", minTime);
return 0;
}
Мне удалось выжать почти 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
Если понимать, как работает векторизация, то можно "наивный" код довольно легко дотюнить до векторизуемого состояния. Получается короче интринсиков и совместимо с разными системами команд.
https://gcc.godbolt.org/z/jbrqGKxnx
(хвост проигнорирован для простоты)
Gcc всё сводит к вызову memset (call memset@PLT), видимо у неё внутри SIMD есть.
Можно Clang смотреть, там SIMD в явном виде (строки 72-74): https://gcc.godbolt.org/z/PPPz6xv6s
Single и Double. Extended в SSE вообще нет, так что в x64 Extended = Double.
Для Single желательно использовать {$EXCESSPRECISION OFF}, иначе расчёты пытается делать в Double и тормозит из-за постоянных преобразований.
Каких-то чудес ждать не стоит, это скалярный SSE без векторизации, ускорение заметно в основном из-за неэффективности старого FPU-кодогенератора (и в целом FPU на современном железе).
Если у вас расчёты в плавающей точке, то справедливости ради, в современных Дельфях в x64 стало получше, т.к. перешли с FPU на SSE2. Можно рассчитывать на ускорение раза в 2.
Но конечно, у С/C++ прогресс компилятора за то же время гораздо больше (автовекторизация и пр.). По той простой причине, что в них вкладывается куда больше сил и денег, чем в непопулярные Паскали.
Да, я заметил, но при желании легко исправить на полную сортировку, нужно переделать функцию Sort(__m128i a[9]).
Там сейчас 19 сравнений. Пишут, что для полной сортировки 9 элементов нужно 25, схема есть:
https://www.sciencedirect.com/science/article/pii/S0022000015001397
Как раз преимущество подхода в том, что легко переделать на разные сортировочные сети, не придумывая каждый раз схему перемещения элементов в xmm-регистрах.
Ещё в медианном фильтре картинок этот алгоритм применяется:
https://habr.com/ru/articles/204682/
там кстати код попроще выглядит, без перетасовок в xmm-регистрах. Используется векторизация в направлении "по массиву" (пикселей) - алгоритм абсолютно аналогичен скалярному, только вместо одного значения работаем с пачками по 16-32 элементов. Поэтому получается всего 6 интринсиков, можно даже и без них, у меня где-то был автовекторизованный вариант.
Но да, это подходит для пикселей, а у вас большой пачки из сортируемых фрагментов может и не быть в наличии.
Покажу как я прикручивал инлайн к кардтрейсеру. И конечно проверял - да, заинлайнилось, но медленнее на 2 секунды.
И что не так?
Какие конкретно? Про SSE я знаю и объяснил, почему это не сработало.
И речь шла о том, что некоторые ускоряют код вообще без изменений. Именно это я и называю "прогресс в компиляторе". Либо сильно ускоряют при умеренных изменениях (затачивание под векторизацию).
Единственное, что в Дельфи можно придумать с умеренными изменениями, это распараллеливание циклов. Но если очень захотеть, то можно их параллелить и в D5, хотя это и неудобно без анонимок.
Инлайн работает криво и довольно часто замедляет вместо ускорения. И кардтрейсер у меня тоже замедлил.
У меня есть проект, который собирается разными версиями Дельфи начиная с D5/1999, недавно протестировал его на D12/2023.
И какая же разница в скорости исполняемого кода за 24 года?
Никакой! Ну может 1%, на уровне погрешности.
Потому что там в основном целочисленные расчёты, и единственное заметное улучшение компилятора (плавающая точка на SSE в x64) не влияет.
Так вот и получается, что для моих целей прогресса в компиляторе нет, и он застрял даже можно сказать в 90-х.
Андроиды и прочие платформы не особо интересны, разве что Линукс... впрочем, по сообщениям там ещё хуже со скоростью, потому что это криво (с отключенной оптимизацией) прикрученный LLVM. Кто-то может проверить кардтрейсер на Дельфи/Линуксе?
На Си у меня нет проекта в точности аналогичного дельфийскому, но тот же кардтрейсер на gcc версий 4.9 и 12 (между ними 8 лет) показывает на разных ф-ях ускорение 10-20%, а иногда 2+ раза, если новая версия векторизовала, а старая нет. С компилятором 1999 года разница должна быть ещё больше хотя бы из-за поддерживаемого набора инструкций.
Ну что касается "написать правильно", то например C++ можно относительно небольшими правками разогнать в разы, я про это целую статью писал:
https://habr.com/ru/articles/685228/
Дельфи от этих правок ускоряется гораздо меньше, потому что векторизацию компилятор не умеет совсем. Хотите SIMD - только хардкор, только ассемблерные простыни вручную.
Навскидку на этом:
https://github.com/Mark-Kovalyov/CardRaytracerBenchmark
получаю С# x64 - 9.4, Delphi x64 - 16.8 секунды (однопоток, консольный вывод в Дельфи отключен). Меньше - лучше, то есть С# быстрее почти в 2 раза.
Почему? Ну, JIT C# основан вероятно на C++, а кодогенерация в плюсовых компиляторах со временем улучшается. У Дельфи она застряла на уровне 2000-х по причине неустроенности (перепродажи) и нехватки ресурсов на разработку.
Я понимаю, почему вы держитесь за написанные и отлаженные млн. строк, просто слегка просвещаю насчёт современного соотношения сил.
Notepad++ даёт небольшую задержку при запуске. Да, едва заметную, может полсекунды, но разница с AkelPad чувствуется. Дело вкуса, но мне хотелось бы мгновенной реакции от контекстного вьюера всякой txt-мелочи.
Тоже когда-то пользовался Bred, потом перешёл на AkelPad - всё то же самое + подсветка, 600 кб.
Сейчас проверил новые версии - уже и автодополнение есть, и сворачивание кода при макс. размере 4.5 мб. Но это плагины, при желании можно пользоваться как блокнотом.
64 Гб/c у Pci-Ex 4 - это суммарная в обе стороны, в одну сторону 32 Гб/c. По моим тестам реальная скорость в оптимальном для OpenCL варианте (pinned memory) около 75% от максимальной, 23-24 Гб/c на GF3080. Возможно, серией асинхронных запросов с разными картинками можно выжать больше, у меня гонялась одна.
Тесты с обработкой я тоже делал, и получалось, что единичные простые операции (напр. gauss blur) всё-таки нет смысла гонять в видеокарту. Чем сложнее, тем лучше: AHD debayer - умеренное ускорение раза в два, нейросети - да, самое то :)
Или переносить на GPU весь конвейер из простых операций. При этом отлаживать и поддерживать GPU-код сложнее.
Советы компиляторы уже дают, в godbolt для Clang есть окно Optimization (через кнопку +).
Насчёт loop distribution он оказался прав, но сам не осилил, пришлось вручную делать.
( https://habr.com/ru/articles/685228/ )
"реализация GetEvensCount без if всё-таки чуть-чуть медленнее, чем с if'ами"
Хотя это и несколько оффтоп, но попиарю в очередной раз компилятор Clang:
https://gcc.godbolt.org/z/WoMbrx8zx
SIMD, развёртка, считаем 32 числа за итерацию - эта версия уж точно не медленнее.
Значит, в общем случае избавляться от if выгоднее.
Только я вставлял в сишный проект, поэтому переправил всё с плюсов на чистый Си. Но вряд ли это влияет. И я тоже ожидал, что будет мухлевать с итерациями, но нет, время явно зависит от Cycles.
код
Мне удалось выжать почти 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