Обновить
8

Пользователь

1
Подписчики
Отправить сообщение

Я подобное писал вручную и при правильном использовании оно ускоряет. И предлагаю смотреть не в код, а в ассемблер. Клод ошибся и выдал неоптимальный вариант - ну так нейросети в принципе ошибаются, и ошибка в интринсиках скорее уронила бы всё нафиг. Не вижу большой проблемы в том, чтобы поправить результат сети.

Дошли руки протестировать: https://gcc.godbolt.org/z/xqb9GosTe (в godbolt время выполнения нестабильно, но можно потыкать Rerun снизу окна Executor чтоб у лучшего варианта было примерно 0.220, или собирайте локально)

Модифицированный Клод (IsSpanBlankMod) уступает интринсикам, но обгоняет всё остальное. С -O2 вместо -O3 обгоняет всё, но это странно, не буду записывать такое в заслуги.

Интересно было бы посмотреть ваш финальный тест, как там в Win64 получилась разница 8 раз между лучшим и худшим результатом (Win32 даже не комментирую). Видимо, тест жёстко заточен под SIMD и/или компилятор проваливает все варианты, где уже не оптимизировано за него вручную.

Мне этот код показался подозрительным тем, что на самом деле байты обрабатываются всё так же последовательно. Вложенный цикл не несёт чего-то полезного.

Смысл вложенного в том, чтобы вынести условный выход из цикла во внешний. Только так условные переходы с векторизацией и делаются, раз в 8/16/32 элементов, а не для каждого элемента. В коде Дипсика это же и написано, только другими словами. И ассемблер с парой правок (|| -> |, 8 -> 16) практически такой же: https://gcc.godbolt.org/z/T5hvszb9z

Так что это вполне себе метод векторизации, хотя и да, сильно зависимый от компилятора.

я тоже не замерял разницу O2 и O3, указал O2 потому что на MSVC нет О3

Проверил ради интереса на этом тесте:
https://habr.com/ru/articles/685228/
разница между -O3 и -O2 у Clang 5%. MSVC в 2022 отставал от Clang с любыми опциями (см. последний раздел).

Я когда-то тоже довольно долго возился с асм-простынями, потом посмотрел на godbolt.org, что выдают компиляторы C/C++, и понял, что нет смысла. По соотношению "скорость кода/скорость разработки" гораздо эффективнее подтюнить сишный код, чтобы компилятор выдал около-оптимальный асм, чем пытаться писать идеально вручную. Также код получается компактнее/читабельнее/переносимее, и какого-то ужасного UB в небольших функциях вы скорее всего не наловите.
Рассматривайте это как шейдеры/кернелы CUDA - их же пишут на диалекте Си даже те, кто Си не любит. А куда денешься, нет шейдеров на Го, и шейдерный ассемблер существовал недолго.

исходный код на Delphi был не очень качественным

Не обязательно, просто x86-компилятор Дельфи использует FPU для плавающей точки, ещё и не самым оптимальным образом. Если аналог на Си векторизовался с AVX, то вполне может быть 10 раз.

Из gccgo можно выжать векторизацию, но она такая смешная получается, с векторизованной проверкой границ, как я понял. Cерии сложение + несколько сравнений - это же оно?
https://godbolt.org/z/6fPxf3bT6
Как отключить - не в курсе, -B тут не работает.
А в стандартном компиляторе сложных оптимизаций вроде как нет (хотя я не специалист, может чего-то не знаю).

Я не настоящий плюсовик :) И правда, там же 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 интринсиков, можно даже и без них, у меня где-то был автовекторизованный вариант.
Но да, это подходит для пикселей, а у вас большой пачки из сортируемых фрагментов может и не быть в наличии.

Покажу как я прикручивал инлайн к кардтрейсеру. И конечно проверял - да, заинлайнилось, но медленнее на 2 секунды.

И что не так?
  function Add(Const v1, v2: TVector): TVector; overload; inline;
  begin
    Result.x := v1.x + v2.x;
    Result.y := v1.y + v2.y;
    Result.z := v1.z + v2.z;
  end;

  function Dot(Const v1, v2: TVector): TFloat; overload; inline;
  begin
    Result := v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
  end;

  function Dot(Const v1: TVector): TFloat; overload; inline;
  begin
    Result := v1.x * v1.x + v1.y * v1.y + v1.z * v1.z;
  end;

  function Init(x,y,z : TFloat): TVector; inline;
  begin
    Result.x := x; Result.y := y; Result.z := z;
  end;

// в tracer:
        p := o + TVector.Create(-k, 0, -(j + 4));
        b := p * d;
        c := p * p - 1;
        q := b * b - c;
// ->
        p := Add(o, Init(-k, 0, -(j + 4)));
        b := Dot(p, d);
        c := Dot(p) - 1;
        q := b * b - c;

Новые (другие) типы. Также, использовать новую математику, интринсики и т.д.

Какие конкретно? Про 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-мелочи.

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность