Не знаю, как вы ухитрились получить 10 раз, возможно, компилятор не понял Ofast и не оптимизировал. Я бы предположил, что большого ускорения не будет, потому что умножение матриц компиляторы давно векторизуют сами, причём под любую систему команд, включая FMA и AVX512, про которые выше писали. У gcc прям красивый ассемблер получается, clang почему-то чуть хуже: https://gcc.godbolt.org/z/91b9M4GeM Правда, пол-функции - это перетасовки, чтобы использовать полную длину AVX-регистров. Не факт, что это быстро на самом деле, ну напишите -msse4 вместо -march=haswell, будет примерно копия ваших sse-интринсиков.
Я не настоящий плюсовик :) И правда, там же 0.f. Автор статьи ошибся, а я скопипастил, сходу не разглядев.
С интом да, всё отлично. C флоатами странная ситуация, почему-то и ручную реализацию не хочет векторизовать, пока не пнёшь специальной директивой. https://gcc.godbolt.org/z/7qqhcq6ox
В окне Opt Viewer раньше писал причину, почему нет, сейчас ничего внятного не пишет.
vaddss хотя и относится к AVX, но это скалярная команда, векторная будет vaddps. Да и видно, что счётчик цикла наращивается на 4, на 1 флоат/инт.
По части "оптимальной реализации", загнал первый пример в Годболт: https://gcc.godbolt.org/z/8KhPxe1M4 рукописный код векторизует, STL-ный - нет. Мало того, STL ещё и считает сумму в плавающей точке.
Если понимать, как работает векторизация, то можно "наивный" код довольно легко дотюнить до векторизуемого состояния. Получается короче интринсиков и совместимо с разными системами команд. 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;
}
Не знаю, как вы ухитрились получить 10 раз, возможно, компилятор не понял Ofast и не оптимизировал.
Я бы предположил, что большого ускорения не будет, потому что умножение матриц компиляторы давно векторизуют сами, причём под любую систему команд, включая FMA и AVX512, про которые выше писали. У gcc прям красивый ассемблер получается, clang почему-то чуть хуже: https://gcc.godbolt.org/z/91b9M4GeM
Правда, пол-функции - это перетасовки, чтобы использовать полную длину AVX-регистров. Не факт, что это быстро на самом деле, ну напишите -msse4 вместо -march=haswell, будет примерно копия ваших sse-интринсиков.
Попиарю тогда уж и свою статью, там как раз про затачивание кода под автовекторизатор:
https://habr.com/ru/articles/685228/
Я не настоящий плюсовик :) И правда, там же 0.f. Автор статьи ошибся, а я скопипастил, сходу не разглядев.
С интом да, всё отлично. C флоатами странная ситуация, почему-то и ручную реализацию не хочет векторизовать, пока не пнёшь специальной директивой. https://gcc.godbolt.org/z/7qqhcq6ox
В окне Opt Viewer раньше писал причину, почему нет, сейчас ничего внятного не пишет.
vaddss хотя и относится к AVX, но это скалярная команда, векторная будет vaddps. Да и видно, что счётчик цикла наращивается на 4, на 1 флоат/инт.
По части "оптимальной реализации", загнал первый пример в Годболт:
https://gcc.godbolt.org/z/8KhPxe1M4
рукописный код векторизует, STL-ный - нет. Мало того, STL ещё и считает сумму в плавающей точке.
Если понимать, как работает векторизация, то можно "наивный" код довольно легко дотюнить до векторизуемого состояния. Получается короче интринсиков и совместимо с разными системами команд.
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.
код