По поводу обработки границ у Гаусса, отмечу, что в OpenCL есть images, они же текстуры из 3D API. При выборке из текстуры края обрезаются автоматически, также есть трюки, позволяющие сократить число выборок за счёт бесплатной билинейной интерполяции. Конечно, images не будут ничего ускорять, если запускать кернел на CPU - там будет работать софтверная имитация.
Ну не скажу, что хорошо организован, автоматической сборки нет. И не такие большие объёмы кода переписаны на Си.
Для прототипирования очень удобен онлайн-компилятор https://gcc.godbolt.org/ Тем более, что я не люблю интринсики и упираю на (полу)автоматическую векторизацию - там сразу видно качество этой векторизации. Компиляторы - gcc или Clang, Clang обычно предпочтительнее, т.к. лучше поддерживает векторные расширения. Но иногда gcc выдаёт лучший результат.
А в оффлайне делается dll-ка с помощью IDE CodeBlocks + опять же gcc или Clang. Последние версии компиляторов ставятся на Винду через пакетный менеджер msys2. gcc можно также скачать из проекта mingw-w64, но не последней версии.
Как вариант, можно линковать obj, причём в 64 битах вроде бы линкуется вообще безо всяких конверсий. Но давно не использовал этот метод, отладка совсем никакая (dll можно отлаживать из CodeBlocks с дельфийским host application).
Почему не MSVC? Ну, gcc/Clang мне кажутся позадорнее в плане оптимизации (но возможно, я плохо знаю опции MSVC), и в MSVC нет векторных расширений. Есть возможность поставить в IDE MSVC компилятор Clang, но я пока не пробовал и не знаю, насколько этот Clang полноценный, поддерживает ли расширения.
Я понимаю, что всё это кажется сложным и муторным, но поиграешься с онлайн-компилятором и обнаружишь, с какой легкостью он выдаёт огромные SIMD-простыни, которые ты раньше писал вручную... причём для любой разрядности и системы команд... и возвращаться к Дельфи-ассемблеру уже совсем тоскливо.
Ну и выше я много писал о том, что давно забросил использование SIMD через дельфийский ассемблер (кроме совсем мелких функций), использую для этого компиляторы Си. Там если что-то передаётся в функцию, то как правило крупными кусками - массивы, структуры. Их можно либо выровнять вручную, либо заставить сишный компилятор использовать movups.
Кстати, можно и в чисто дельфийском коде этот метод применить - собираем все глобальные переменные в структуру (запись) и вручную её выравниваем. Точнее, используем указатель на запись, который выравнен в блоке памяти [размер+16].
А арифметика с операндом в памяти вот прямо заметно лучше, чем movups в регистр и та же операция с регистром? Я не проверял, но мне кажется, что разница будет пара процентов. Выборка из памяти никуда не денется в любом случае. И это имеет большее значение, чем +1 инструкция. И кстати, как в 64-битном режиме - тоже ничего не выравнивает? Могу ошибаться, но там вроде есть требование от ОС по выравниванию стека.
Хм, правда болезненная? По-моему на современных процессорах выравнивание уже не особо влияет. Я получал разницу порядка 10% (для небольшого промежуточного буфера). Если уж хочется выжать эти 10%, то в Delphi FastMM умеет выравнивать на 16 со специальной опцией, при желании можно сделать выравнивание вручную. При этом в коде можно всегда использовать команды без выравнивания, начиная с первых Core i на реально выровненных данных у movups такая же скорость, как у movaps.
В SIMD нет инструкции целочисленного деления. Векторизовать его может только ICC со встроенной в компилятор библиотекой интринсиковых трюков (SVML), да и то для простых чисел ещё нужно убрать выход из цикла на каждой итерации. Поэтому простые числа - плохой тест на оптимизацию кода компилятором, сложно на нём развернуться. В "жизни" не так часто бывает, чтобы всё упиралось в какую-то одну операцию.
Я собирался использовать не классический компилятор Билдера, конечно, а как раз Clang-based. Который по идее должен уметь векторизацию, развёртку циклов, эффективный инлайн - во всяком случае оригинальный Clang умеет. И тормозит он не (не только) потому, что у разработчиков руки кривые, а потому что там гораздо более сложная оптимизация.
А есть ли хоть какие-то преимущества у C++Builder перед Delphi для Windows-разработки?
Я думал об использовании C-Builder в дополнение к Дельфи для оптимизации узких мест. То есть основная программа на Дельфи (более простой и приятный язык), а то, что раньше делалось ассемблерными вставками, делается функциями на Си (который всё-таки более читабельный по сравнению с ассемблером). Си как замена ассемблеру - назад в 1970-е :) Но это имеет смысл только если вам нужна вот такая суровая оптимизация, с ассемблерными или сишными вставками. Для большинства Дельфи-проектов (разного рода морд к БД) - очевидно, не нужна. И C-Builder в конечном итоге оказался для этого неудобен, использую сишный код через dll, собранную CodeBlocks и Clang/gcc.
Быстрое сжатие картинок без потерь - отлично, давно такое ищу.
Хотя надо отметить, что обогнать сжатие в png - невелика заслуга, там оно медленное by design, т.к. "фильтр" для строки (преобразование, улучшающее сжимаемость) выбирается методом перебора. В зависимости от степени сжатия может перебираться больше или меньше разных фильтров, поэтому на макс. сжатии png очень медленный, на минимальном, без фильтров, это фактически тот же zip.
Интересно было бы сравнить Quite OK Image с ImageZero - тоже никому не известный алгоритм. В статье на Хабре заявлено аж 35-кратное превосходство в скорости над png (хм, может быть, автор QOI переизобрёл ImageZero заново?). Когда я сравнивал ImageZero с png, ускорение получалось меньше, в 2-4 раза, но зато некоторые картинки сжимались в 1.5 раза лучше. Возможно, png тестировался не на максимальном сжатии, точно не помню.
В целом ImageZero явно лучше png (точнее, алгоритмов сжатия png), но спустя 9 лет по-прежнему никому не известен...
а именно про интринсики и нет ничего. Только вот это:
Как раз интринсики изрядно заезженная тема, возьмите любую статью по SIMD, и там будут интринсики. Переносимость у них по-моему посредственная, вы не сможете даже скомпилировать AVX-интринсики под SSE, не то что под другой процессор.
Менее известны векторные расширения (gcc, Clang, также ICC поддерживает gcc-нотацию). Здесь я выкладывал пример, полная версия.
А не пробовали положить все данные в один буфер? Вот как здесь 2-й и 3-й варианты: www.khronos.org/opengl/wiki/Vertex_Specification_Best_Practices#Formatting_VBO_Data
Мне 3-й метод кажется более предпочтительным, т.к. он в целом более популярен (используется в DirectX), и видеокарты должны быть лучше заточены под такое расположение данных. Хотя это только предположение, не проверял.
Также отмечу, что если изначально у вас объёмные данные, которые можно представить 3D-массивом, то их можно визуализировать вообще без использования вершин и полигонов, через volume raycasting. habr.com/ru/post/123632
Опасается, что области памяти, с которыми работаем, могут пересекаться и векторизовать нельзя. Проанализировать код и понять, что нет — пока видимо ума не хватает.
__restrict как раз и говорит ему, что не пересекаются.
Статичные массивы вроде float arr[1024*1024*64] векторизует и так. gcc.godbolt.org/z/5hP9vYanG
Сравнил в том виде, в каком выкладывал в godbolt (Clang / gcc / интринсики, мс).
i3-3250 (IvyBridge), AVX: 69 / 45 / 44
i9-9900K (CoffeeLake), AVX: 52 / 38 / 40
i9-9900K (CoffeeLake), FMA: 44 / 38 / 40
Clang отстал на 10-60%, а gcc даже обогнал интринсики. В горизонтальные сложения он тоже не умеет, но зато сообразил перетасовать данные (матрицу 8*8?) ДО цикла. gcc.godbolt.org/z/PWjar9qar
Что логично, я тоже об этом думал, глядя на попытки Clang-а, и в статьях по умножению матриц на Хабре что-то такое было. Похоже, gcc уже настолько крут, что читает Хабр :)
Если серьёзно, есть с gcc одна странность — если вызывать функцию idctAV в вашем примере 1 раз, то работает быстро, а если несколько раз в цикле для большей стабильности результата, то скорость сильно проседает (только с gcc, с clang такого нет).
Код
const int cycles = 10;
auto start = high_resolution_clock::now();
for (size_t c = 0; c < cycles; ++c)
idctAV((float*)&arr[0], (float*)&output[0], (float*)&idctMat[0]);
auto end = high_resolution_clock::now();
const auto result = std::accumulate(output.begin(), output.end(), 0.0);
std::cout << result << std::endl;
std::cout << duration_cast<milliseconds>((end - start) / cycles).count() << " ms" << std::endl;
Может быть я что-то напортачил с тайпкастами, я плохо знаю эти ваши плюсы. В моём примере проседания по скорости нет, там idctAV вызывается несколько раз, но как функция в DLL.
А ваш изначальный результат — это, как и следовало ожидать, невекторизованный код (простыня из vmovss/vmulss/vaddss). gcc.godbolt.org/z/4jffcb89n
Я попытался ради интереса повторить ваш тест, но не уверен, что правильно оформил интринсиковый вариант: gcc.godbolt.org/z/cbqGhYcas
так правильно или нет? В частности, тип idctRows.
Обычная версия кстати вполне успешно векторизуется: gcc.godbolt.org/z/e4K3Ps5z3
только компилятор не умеет использовать горизонтальное сложение, и из-за этого долго тасует данные в регистрах, чтобы затем выдать красивую «простыню» из mulps/addps. Но сомневаюсь, что это даёт замедление аж в 5-7 раз, скорее вы сравнивали с невекторизованной версией.
У вас для Гаусса 4x4 нет готового кода на Дельфи, который можно взять за образец, либо ассемблер, либо словесное описание.
Здесь я взял за основу 3x3, может сами допишете? gcc.godbolt.org/z/1KKG9a
до общения с Вами пребывал в уверенности, что лучшая оптимизация- в ICC- как никак, компилятор от производителя процессора!
ICC тоже хорош, но у него своя система параметров, и не все из них работают через godbolt. Попробуйте -fast, и будет таки fast, но принудительно включится FMA/AVX2. В общем, я толком не знаю, как с ним обращаться.
когда можно ради упрощения жизни компилеру переломать базовые структуры данных
Да всё уже, отбой, не надо ломать. На практике раздельные массивы «не взлетели». Наверное, виноват не последовательный доступ к памяти, лезем в память по 9-и разным указателям вместо 1-го. Может быть, если бы хранить элементы мини-массивами по 4 штуки, было бы лучше, но это совсем уже неудобно в работе.
От новых инструкций (AVX, FMA) тоже толку немного.
Но в результате я всё-таки обогнал ваш ассемблер примерно на 30%, а для компактной матрицы сильно, раза в 3.
Аккаунта на Гитхабе нет, поэтому архивом, там же и результаты.
забить всю ПСП и пережевывать данные быстрее, чем они через нее пролазят
Не уверен, что если вы забиваете ПСП, то это повод для гордости. Может слишком мало арифметики на чтение/запись? Но да-а, теперь уже не хочется ничего менять, когда всё захардкожено ассемблером.
Лично для меня одной из причин залезть именно в ассемблер было то, что предыдущий опыт использования «ускоряющей» dll был полон мучительной отладки, когда какие-то данные вызвали ошибку в потрохах ДЛЛ, а дебагер не может до них докопаться.
Если это ваша dll-ка, то отлаживать её можно из сишной IDE c дельфийским host application.
Это добавляет сложности, но и отладка ассемблера — тоже так себе удовольствие, особенно через пару лет после того, как вы этот ассемблер писали.
Но совершенно спокойно можно прямо в родной старючей delphi xe4 (не слезая с кактуса)
Хорошо, не слезайте, больше не буду уговаривать :)
г. СLANG- дает вполне компактный и эффективный код, но все равно может пропустить очевидные вещи (как с повторным divsd на одних и тех же данных)
div — это так, досадное недоразумение. Не в div-ах суть. Я даже не буду спрашивать, сколько div-ов на таком коде генерирует Дельфи :) Нормальный разработчик делает замену в коде множественного деления на умножение «на автопилоте».
А суть вот в чём: я переделал код на обработку массива матриц, и теперь векторизатор отработал лучше. Он считает по 2 матрицы за итерацию, за счёт этого все команды цикла векторные.
Причём можно сделать загрузку матриц в регистры эффективнее, если хранить их по-другому: не как массив структур, а как раздельные массивы для каждого элемента матрицы. Смотрите сами, насколько это допустимо в вашей ситуации.
Раздельные массивы хороши тем, что лучше масштабируются на любую систему команд, можно считать по 4 матрицы за итерацию с AVX.
И всё это в 20-30 строчках максимально простого кода, никаких ассемблерных портянок вручную.
периодически выходят скромные статьи с разбором профайлингов и сказками про то, как какие-то одинокие самоучки делают умножение матриц на CUDE лучше, чем отдел разработки NVidia. На хабре было несколько примеров
Мне кажется, конкретно операции с матрицами — очень ходовая и часто используемая штука, код стандартных библиотек должен быть вылизан до блестящего состояния и соревноваться с ними сложно.
Даже Ермолаев, при всей продвинутости его оптимизации, пишет в комментариях, что предположительно уступает MKL 5-10%.
Возможно, в вашем случае действительно будут влиять накладные расходы на вызов, у вас матрицы мелкие. В MKL для них предусмотрен какой-то хитрый инлайн, но при вызове из Дельфи он конечно работать не будет. Хотя можно написать промежуточную dll на Си, которая реализует функцию обработки массива матриц, с заинлайненной ф-ей MKL.
Мне также попадался одиночка, который заявляет, что у него быстрее, но для матриц общего вида — незначительно, те же 10% (transform inverse это, как я понимаю, упрощённый алгоритм для матриц трансформации в 3D-графике)
про читабельность на Си- я смотрел всякие портянки из интринсиков- честно- не вижу я там читабельности
Интринсики не особо читабельны, согласен.
Я обычно либо пытаюсь подтюнить код под автовекторизацию (это возможно, если понимать её логику и ограничения), либо использую векторные расширения Clang/GCC, они позволяют писать в «шейдерном» стиле. Ну знаете, в шейдерах/OpenCL/CUDA есть типы вроде float4, с которыми можно делать математику как с обычными float. Здесь тот же принцип.
А почему нельзя без сырцов?
Вы неявно используете массу dll из Винды, и для них тоже нет сырцов.
Предлагать перейти на компиляторы Си- это, простите, моветон
Ну да, дельфистам сразу слышатся отголоски холиваров «Дельфи vs С++».
Нет, я предлагаю менять не Дельфи, а ассемблер на Си.
А в случае с обращением матрицы- я в принципе не могу родить код (ни на С, ни на Fortran, ни на Pascal), который можно упаковать в одни регистры
В регистры AVX (или скажем AVX-512) влезает гораздо больше.
хочу. меня интересует скорость обращения массива из 1млн матриц 4*4*FP64 в 1 поток на 1 ядре
Поскольку это ваша задача, то давайте вы напишете тестовую программу на Дельфи, а я к ней прикручу сишный вызов. Вам виднее, какие матрицы должны быть, какие примеры данных. Это и как приложение к статье будет полезно.
Но главное- у меня только один divsd, а по Вашей ссылке- два дива
С делением нехорошо получилось, да. Но это же легко правится вручную, дописать одну строку D = 1 / D и поменять деление на умножение. Или переключиться на систему команд AVX (-mavx) или AVX2/FMA (-march=haswell), будет одно деление.
В целом я не исключаю, что может быть медленнее, в конце концов, я на эту «оптимизацию» потратил 2-3 минуты. А вы на свою сколько дней?
Даже если придётся векторизовать вручную, на Си это будет компактнее, читабельнее и более гибко, можно легко переключаться между 32/64 битами и относительно легко — между наборами команд.
По поводу обработки границ у Гаусса, отмечу, что в OpenCL есть images, они же текстуры из 3D API. При выборке из текстуры края обрезаются автоматически, также есть трюки, позволяющие сократить число выборок за счёт бесплатной билинейной интерполяции.
Конечно, images не будут ничего ускорять, если запускать кернел на CPU - там будет работать софтверная имитация.
Ну не скажу, что хорошо организован, автоматической сборки нет. И не такие большие объёмы кода переписаны на Си.
Для прототипирования очень удобен онлайн-компилятор https://gcc.godbolt.org/
Тем более, что я не люблю интринсики и упираю на (полу)автоматическую векторизацию - там сразу видно качество этой векторизации.
Компиляторы - gcc или Clang, Clang обычно предпочтительнее, т.к. лучше поддерживает векторные расширения. Но иногда gcc выдаёт лучший результат.
А в оффлайне делается dll-ка с помощью IDE CodeBlocks + опять же gcc или Clang. Последние версии компиляторов ставятся на Винду через пакетный менеджер msys2. gcc можно также скачать из проекта mingw-w64, но не последней версии.
Как вариант, можно линковать obj, причём в 64 битах вроде бы линкуется вообще безо всяких конверсий. Но давно не использовал этот метод, отладка совсем никакая (dll можно отлаживать из CodeBlocks с дельфийским host application).
Почему не MSVC? Ну, gcc/Clang мне кажутся позадорнее в плане оптимизации (но возможно, я плохо знаю опции MSVC), и в MSVC нет векторных расширений.
Есть возможность поставить в IDE MSVC компилятор Clang, но я пока не пробовал и не знаю, насколько этот Clang полноценный, поддерживает ли расширения.
Я понимаю, что всё это кажется сложным и муторным, но поиграешься с онлайн-компилятором и обнаружишь, с какой легкостью он выдаёт огромные SIMD-простыни, которые ты раньше писал вручную... причём для любой разрядности и системы команд... и возвращаться к Дельфи-ассемблеру уже совсем тоскливо.
Ну и выше я много писал о том, что давно забросил использование SIMD через дельфийский ассемблер (кроме совсем мелких функций), использую для этого компиляторы Си. Там если что-то передаётся в функцию, то как правило крупными кусками - массивы, структуры. Их можно либо выровнять вручную, либо заставить сишный компилятор использовать movups.
Кстати, можно и в чисто дельфийском коде этот метод применить - собираем все глобальные переменные в структуру (запись) и вручную её выравниваем. Точнее, используем указатель на запись, который выравнен в блоке памяти [размер+16].
А арифметика с операндом в памяти вот прямо заметно лучше, чем movups в регистр и та же операция с регистром?
Я не проверял, но мне кажется, что разница будет пара процентов. Выборка из памяти никуда не денется в любом случае. И это имеет большее значение, чем +1 инструкция.
И кстати, как в 64-битном режиме - тоже ничего не выравнивает? Могу ошибаться, но там вроде есть требование от ОС по выравниванию стека.
Хм, правда болезненная?
По-моему на современных процессорах выравнивание уже не особо влияет. Я получал разницу порядка 10% (для небольшого промежуточного буфера). Если уж хочется выжать эти 10%, то в Delphi FastMM умеет выравнивать на 16 со специальной опцией, при желании можно сделать выравнивание вручную.
При этом в коде можно всегда использовать команды без выравнивания, начиная с первых Core i на реально выровненных данных у movups такая же скорость, как у movaps.
В SIMD нет инструкции целочисленного деления.
Векторизовать его может только ICC со встроенной в компилятор библиотекой интринсиковых трюков (SVML), да и то для простых чисел ещё нужно убрать выход из цикла на каждой итерации.
Поэтому простые числа - плохой тест на оптимизацию кода компилятором, сложно на нём развернуться. В "жизни" не так часто бывает, чтобы всё упиралось в какую-то одну операцию.
Я собирался использовать не классический компилятор Билдера, конечно, а как раз Clang-based. Который по идее должен уметь векторизацию, развёртку циклов, эффективный инлайн - во всяком случае оригинальный Clang умеет.
И тормозит он не (не только) потому, что у разработчиков руки кривые, а потому что там гораздо более сложная оптимизация.
Я думал об использовании C-Builder в дополнение к Дельфи для оптимизации узких мест. То есть основная программа на Дельфи (более простой и приятный язык), а то, что раньше делалось ассемблерными вставками, делается функциями на Си (который всё-таки более читабельный по сравнению с ассемблером). Си как замена ассемблеру - назад в 1970-е :)
Но это имеет смысл только если вам нужна вот такая суровая оптимизация, с ассемблерными или сишными вставками. Для большинства Дельфи-проектов (разного рода морд к БД) - очевидно, не нужна.
И C-Builder в конечном итоге оказался для этого неудобен, использую сишный код через dll, собранную CodeBlocks и Clang/gcc.
Быстрое сжатие картинок без потерь - отлично, давно такое ищу.
Хотя надо отметить, что обогнать сжатие в png - невелика заслуга, там оно медленное by design, т.к. "фильтр" для строки (преобразование, улучшающее сжимаемость) выбирается методом перебора. В зависимости от степени сжатия может перебираться больше или меньше разных фильтров, поэтому на макс. сжатии png очень медленный, на минимальном, без фильтров, это фактически тот же zip.
Интересно было бы сравнить Quite OK Image с ImageZero - тоже никому не известный алгоритм. В статье на Хабре заявлено аж 35-кратное превосходство в скорости над png (хм, может быть, автор QOI переизобрёл ImageZero заново?). Когда я сравнивал ImageZero с png, ускорение получалось меньше, в 2-4 раза, но зато некоторые картинки сжимались в 1.5 раза лучше. Возможно, png тестировался не на максимальном сжатии, точно не помню.
В целом ImageZero явно лучше png (точнее, алгоритмов сжатия png), но спустя 9 лет по-прежнему никому не известен...
Как раз интринсики изрядно заезженная тема, возьмите любую статью по SIMD, и там будут интринсики. Переносимость у них по-моему посредственная, вы не сможете даже скомпилировать AVX-интринсики под SSE, не то что под другой процессор.
Менее известны векторные расширения (gcc, Clang, также ICC поддерживает gcc-нотацию). Здесь я выкладывал пример, полная версия.
Вот ещё пример успешной автовекторизации с незаурядной перетасовкой данных в исполнении gcc.
В своё время начинал писать статью про "высокоуровневую" векторизацию, но забросил. Может, как-нибудь допишу.
www.khronos.org/opengl/wiki/Vertex_Specification_Best_Practices#Formatting_VBO_Data
Мне 3-й метод кажется более предпочтительным, т.к. он в целом более популярен (используется в DirectX), и видеокарты должны быть лучше заточены под такое расположение данных. Хотя это только предположение, не проверял.
Также отмечу, что если изначально у вас объёмные данные, которые можно представить 3D-массивом, то их можно визуализировать вообще без использования вершин и полигонов, через volume raycasting.
habr.com/ru/post/123632
__restrict как раз и говорит ему, что не пересекаются.
Статичные массивы вроде float arr[1024*1024*64] векторизует и так.
gcc.godbolt.org/z/5hP9vYanG
i3-3250 (IvyBridge), AVX: 69 / 45 / 44
i9-9900K (CoffeeLake), AVX: 52 / 38 / 40
i9-9900K (CoffeeLake), FMA: 44 / 38 / 40
Clang отстал на 10-60%, а gcc даже обогнал интринсики. В горизонтальные сложения он тоже не умеет, но зато сообразил перетасовать данные (матрицу 8*8?) ДО цикла.
gcc.godbolt.org/z/PWjar9qar
Что логично, я тоже об этом думал, глядя на попытки Clang-а, и в статьях по умножению матриц на Хабре что-то такое было. Похоже, gcc уже настолько крут, что читает Хабр :)
Если серьёзно, есть с gcc одна странность — если вызывать функцию idctAV в вашем примере 1 раз, то работает быстро, а если несколько раз в цикле для большей стабильности результата, то скорость сильно проседает (только с gcc, с clang такого нет).
А ваш изначальный результат — это, как и следовало ожидать, невекторизованный код (простыня из vmovss/vmulss/vaddss).
gcc.godbolt.org/z/4jffcb89n
gcc.godbolt.org/z/cbqGhYcas
так правильно или нет? В частности, тип idctRows.
Обычная версия кстати вполне успешно векторизуется:
gcc.godbolt.org/z/e4K3Ps5z3
только компилятор не умеет использовать горизонтальное сложение, и из-за этого долго тасует данные в регистрах, чтобы затем выдать красивую «простыню» из mulps/addps. Но сомневаюсь, что это даёт замедление аж в 5-7 раз, скорее вы сравнивали с невекторизованной версией.
Здесь я взял за основу 3x3, может сами допишете?
gcc.godbolt.org/z/1KKG9a
Да всё уже, отбой, не надо ломать. На практике раздельные массивы «не взлетели». Наверное, виноват не последовательный доступ к памяти, лезем в память по 9-и разным указателям вместо 1-го. Может быть, если бы хранить элементы мини-массивами по 4 штуки, было бы лучше, но это совсем уже неудобно в работе.
От новых инструкций (AVX, FMA) тоже толку немного.
Но в результате я всё-таки обогнал ваш ассемблер примерно на 30%, а для компактной матрицы сильно, раза в 3.
Аккаунта на Гитхабе нет, поэтому архивом, там же и результаты.
Не уверен, что если вы забиваете ПСП, то это повод для гордости. Может слишком мало арифметики на чтение/запись? Но да-а, теперь уже не хочется ничего менять, когда всё захардкожено ассемблером.
Если это ваша dll-ка, то отлаживать её можно из сишной IDE c дельфийским host application.
Это добавляет сложности, но и отладка ассемблера — тоже так себе удовольствие, особенно через пару лет после того, как вы этот ассемблер писали.
Хорошо, не слезайте, больше не буду уговаривать :)
А суть вот в чём: я переделал код на обработку массива матриц, и теперь векторизатор отработал лучше. Он считает по 2 матрицы за итерацию, за счёт этого все команды цикла векторные.
Причём можно сделать загрузку матриц в регистры эффективнее, если хранить их по-другому: не как массив структур, а как раздельные массивы для каждого элемента матрицы. Смотрите сами, насколько это допустимо в вашей ситуации.
Раздельные массивы хороши тем, что лучше масштабируются на любую систему команд, можно считать по 4 матрицы за итерацию с AVX.
И всё это в 20-30 строчках максимально простого кода, никаких ассемблерных портянок вручную.
Можно и просто архивом выложить, без Гитхабов.
Даже Ермолаев, при всей продвинутости его оптимизации, пишет в комментариях, что предположительно уступает MKL 5-10%.
Возможно, в вашем случае действительно будут влиять накладные расходы на вызов, у вас матрицы мелкие. В MKL для них предусмотрен какой-то хитрый инлайн, но при вызове из Дельфи он конечно работать не будет. Хотя можно написать промежуточную dll на Си, которая реализует функцию обработки массива матриц, с заинлайненной ф-ей MKL.
Мне также попадался одиночка, который заявляет, что у него быстрее, но для матриц общего вида — незначительно, те же 10% (transform inverse это, как я понимаю, упрощённый алгоритм для матриц трансформации в 3D-графике)
Интринсики не особо читабельны, согласен.
Я обычно либо пытаюсь подтюнить код под автовекторизацию (это возможно, если понимать её логику и ограничения), либо использую векторные расширения Clang/GCC, они позволяют писать в «шейдерном» стиле. Ну знаете, в шейдерах/OpenCL/CUDA есть типы вроде float4, с которыми можно делать математику как с обычными float. Здесь тот же принцип.
Вы неявно используете массу dll из Винды, и для них тоже нет сырцов.
Ну да, дельфистам сразу слышатся отголоски холиваров «Дельфи vs С++».
Нет, я предлагаю менять не Дельфи, а ассемблер на Си.
В регистры AVX (или скажем AVX-512) влезает гораздо больше.
Поскольку это ваша задача, то давайте вы напишете тестовую программу на Дельфи, а я к ней прикручу сишный вызов. Вам виднее, какие матрицы должны быть, какие примеры данных. Это и как приложение к статье будет полезно.
С делением нехорошо получилось, да. Но это же легко правится вручную, дописать одну строку D = 1 / D и поменять деление на умножение. Или переключиться на систему команд AVX (-mavx) или AVX2/FMA (-march=haswell), будет одно деление.
В целом я не исключаю, что может быть медленнее, в конце концов, я на эту «оптимизацию» потратил 2-3 минуты. А вы на свою сколько дней?
Даже если придётся векторизовать вручную, на Си это будет компактнее, читабельнее и более гибко, можно легко переключаться между 32/64 битами и относительно легко — между наборами команд.