Re[7]: Производительность .Net на вычислительных задачах
От: kaa.python Ниоткуда РСДН профессионально мёртв и завален ватой.
Дата: 23.10.20 15:16
Оценка:
Здравствуйте, a_g_99, Вы писали:

__>ну так что же вы тогда не пишите на этом си щарпе. просветите меня. все же хорошо — тяп ляп 5 строчек и С++ на коленях стоит


И на нем пишу, писал вернее. Я вообще на всём что подходит для решения задачи пишу
Re[5]: Производительность .Net на вычислительных задачах
От: vdimas Россия  
Дата: 23.10.20 16:52
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, Nuzhny, Вы писали:

N>>Э, там у тебя в репозитории не самый удачный С++ код, можно его ускорить в пару раз, при этом не факт, что он ещё и увеличится. Не читал код бенчмарка, но явно выделение памяти не надо делать при каждом вызове фильтра,
S>надо. По определению — каждый вызов фильтра возвращает новый массив.

В плюсах предвыделяют память и далее запускают pipeline.
Никто новые массивы не создаёт и не вовращает, ес-но...
По крайней мере, на уровне ответственности конкретного алгоритма.


S>Иначе будем сравнивать яблоки с бананами.


Иначе можно было ограничиться сравнением эффективности вызова new, не усложняя это сравнение всякими linq2d.


S>Что имеется в виду? SIMD на ручных интринсиках? Как раз от этого хотелось бы уйти.


/arch:AVX2?

Но этого мало, надо смотреть, как код бегает по данным, т.е. возможна ли векторизация в том обходе памяти принципиально?
Потому что комплятор зачастую никакой векторизации не делает ввиду её невозможности согласно алгоритма, даже если компилит использование более широкого файла MMX/YMM-регистров... использование файла этих регистров не есть векторизация.
Re[6]: Производительность .Net на вычислительных задачах
От: Sinclair Россия https://github.com/evilguest/
Дата: 24.10.20 03:20
Оценка:
Здравствуйте, vdimas, Вы писали:
V>Иначе можно было ограничиться сравнением эффективности вызова new, не усложняя это сравнение всякими linq2d.
Я не думаю, что два обращения к new на 33 мегапиксела как-то существенно влияют на общий тайминг в ~760ms. На убунте gcc ухитряется опередить дотнет несмотря на new.
Но можно, в принципе, и вынести эту часть работы в дотнетный код.
V>/arch:AVX2?
Да, прикрутил -march=native. Стало чуть быстрее:
|             Method |      FileName |      Mean |    Error |   StdDev | Ratio |
|------------------- |-------------- |----------:|---------:|---------:|------:|
|              CppC4 | p00743.bmp.gz |  48.09 ms | 0.281 ms | 0.263 ms |  0.40 |
|          NaturalC4 | p00743.bmp.gz | 317.74 ms | 0.375 ms | 0.313 ms |  2.64 |
|           UnsafeC4 | p00743.bmp.gz | 120.36 ms | 0.433 ms | 0.361 ms |  1.00 |
|             LinqC4 | p00743.bmp.gz |  60.51 ms | 0.213 ms | 0.178 ms |  0.50 |
| LinqC4VectorCached | p00743.bmp.gz |  50.93 ms | 0.410 ms | 0.364 ms |  0.42 |


|                  Method | WHalf |      FileName |       Mean |    Error |   StdDev | Ratio |
|------------------------ |------ |-------------- |-----------:|---------:|---------:|------:|
|             SafeSauvola |     5 | p00743.bmp.gz | 1,791.4 ms |  1.16 ms |  0.97 ms |  1.70 |
|     UnsafeSauvolaScalar |     5 | p00743.bmp.gz | 1,053.8 ms |  0.47 ms |  0.39 ms |  1.00 |
|              CppSauvola |     5 | p00743.bmp.gz |   559.7 ms |  0.32 ms |  0.29 ms |  0.53 |
|       LinqSauvolaVector |     5 | p00743.bmp.gz | 1,292.8 ms | 12.97 ms | 11.49 ms |  1.22 |
|       LinqSauvolaScalar |     5 | p00743.bmp.gz | 1,599.0 ms |  6.25 ms |  5.54 ms |  1.52 |
| CachedLinqSauvolaVector |     5 | p00743.bmp.gz |   677.9 ms |  0.50 ms |  0.42 ms |  0.64 |
| CachedLinqSauvolaScalar |     5 | p00743.bmp.gz |   997.8 ms |  1.64 ms |  1.37 ms |  0.95 |



V>Но этого мало, надо смотреть, как код бегает по данным, т.е. возможна ли векторизация в том обходе памяти принципиально?

Возможна. Дотнет же её применяет
V>Потому что комплятор зачастую никакой векторизации не делает ввиду её невозможности согласно алгоритма, даже если компилит использование более широкого файла MMX/YMM-регистров... использование файла этих регистров не есть векторизация.
А что, по-вашему, векторизация?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[3]: Производительность .Net на вычислительных задачах
От: AeroSun  
Дата: 24.10.20 09:10
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Здравствуйте, AeroSun, Вы писали:


AS>>В тему не вникал, но все холивары о том, что *ЛюбойЯзык* производительнее С++ (даже с тестами и красивыми графиками) всегда разбиваются о два вопроса:

AS>>1) За счёт чего они быстрее С++?
AS>>2) Почему это не является достижимым на С++?

S>1. Все зависит от компилятора.


Что-то не видно лучших компиляторов, чем у с++

S>2. Сборщик мусора и стратегия кэширования


Это вообще не вопрос для плюсов, к тому же на плюсах это можно делать гораздно гибче

S>3. Более высокоуровневый код упрощает кодирование, отладку и в итоге оптимизацию кода.


Более высокоуровневый код может быть в двух направлениях:

1) В направлении увеличения абстракции — у плюсов тут дело лучше чем подавляющего большинства языков
2) В направлении решения конкретной задачи — тут у плюсов бесшовная возможность выхода на самый низкий уровень программирования делает возможным решение любой задачи, решенной на любом другом языке, вплоть до вида "один-к-одному".

Потому все сравнения можно разделить на 2 вида:
1) сравнения синтаксического сахара. Да у плюсов тут есть проблемки, которые будут решены полностью только при появлении статическая+динамическая рефлексия, а также возможности делать DSL (современные огрызки а-ля "с++ DSL" в расчёт не берём). Но это не сравнение производительности.
2) сравнения реализованной библиотеки/встроенной фичи на языке "Х" с плюсовой реализацией "на коленке". Тут если влить в плюсовую реализацию столько ресурсов, сколько было влито на реализацию языковой фичи/библиотеки в языке "Х" — то как минимум результат будет не хуже, а с большей вероятностью и гораздо лучше.
Re[4]: Производительность .Net на вычислительных задачах
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 24.10.20 10:29
Оценка:
Здравствуйте, AeroSun, Вы писали:

AS>Здравствуйте, Serginio1, Вы писали:


S>>Здравствуйте, AeroSun, Вы писали:


AS>>>В тему не вникал, но все холивары о том, что *ЛюбойЯзык* производительнее С++ (даже с тестами и красивыми графиками) всегда разбиваются о два вопроса:

AS>>>1) За счёт чего они быстрее С++?
AS>>>2) Почему это не является достижимым на С++?

S>>1. Все зависит от компилятора.


AS>Что-то не видно лучших компиляторов, чем у с++

Их столько много, плюс куча настроек

S>>2. Сборщик мусора и стратегия кэширования


AS>Это вообще не вопрос для плюсов, к тому же на плюсах это можно делать гораздно гибче

Можно. Но при этом с кучей настроек не всегда можно выбрать правильные. Нужно шаманить

S>>3. Более высокоуровневый код упрощает кодирование, отладку и в итоге оптимизацию кода.


AS>Более высокоуровневый код может быть в двух направлениях:


AS>1) В направлении увеличения абстракции — у плюсов тут дело лучше чем подавляющего большинства языков

AS>2) В направлении решения конкретной задачи — тут у плюсов бесшовная возможность выхода на самый низкий уровень программирования делает возможным решение любой задачи, решенной на любом другом языке, вплоть до вида "один-к-одному".
Угу. Та же сборка мусора и динамическая компиляция позволяет проще использовать различные подходы.
Например .Net Core, 1C, динамическая компиляция, Scripting API
Плюс Деревья выражений и компиляция Деревья выражений в C# на примере нахождения производной (Expression Tree Visitor vs Pattern matching)

Как там с Linq в С++. Вот пример https://www.linqpad.net/ к произвольной базе произвольный запрос


AS>Потому все сравнения можно разделить на 2 вида:

AS>1) сравнения синтаксического сахара. Да у плюсов тут есть проблемки, которые будут решены полностью только при появлении статическая+динамическая рефлексия, а также возможности делать DSL (современные огрызки а-ля "с++ DSL" в расчёт не берём). Но это не сравнение производительности.
AS>2) сравнения реализованной библиотеки/встроенной фичи на языке "Х" с плюсовой реализацией "на коленке". Тут если влить в плюсовую реализацию столько ресурсов, сколько было влито на реализацию языковой фичи/библиотеки в языке "Х" — то как минимум результат будет не хуже, а с большей вероятностью и гораздо лучше.

Но в итоге люди предпочтут готовые и гибкие инструменты. Просто у управляемых языков со своей средой выполнения намного больше возможностей.
Плюс компиляция .Net Native и CoreRT
и солнце б утром не вставало, когда бы не было меня
Re[7]: Производительность .Net на вычислительных задачах
От: vdimas Россия  
Дата: 24.10.20 16:03
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Но этого мало, надо смотреть, как код бегает по данным, т.е. возможна ли векторизация в том обходе памяти принципиально?

S>Возможна. Дотнет же её применяет

Это если дотнетный алгоритм ходит по данным точно так же...


V>>Потому что комплятор зачастую никакой векторизации не делает ввиду её невозможности согласно алгоритма, даже если компилит использование более широкого файла MMX/YMM-регистров... использование файла этих регистров не есть векторизация.

S>А что, по-вашему, векторизация?

Почему по моему?

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

Не обязательно это делать ручками, для Си есть либы для векторных вычислений, вот парочка популярных вхождений АПИ:
https://software.intel.com/content/www/us/en/develop/documentation/mkl-developer-reference-c/top/blas-and-sparse-blas-routines/blas-routines/blas-level-1-routines-and-functions/cblas-axpy.html#cblas-axpy
https://software.intel.com/content/www/us/en/develop/documentation/mkl-developer-reference-c/top/blas-and-sparse-blas-routines/blas-routines/blas-level-1-routines-and-functions/cblas-dot.html#cblas-dot

Ну и рядом там.

Т.е., в чём тут фишка... Иногда под векторные операции выгодно выделять доп. промежуточную память, например, если эта выделенная память позволит применять векторные инструкции по прямому назначению.

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

Понятно, что если размеры векторов/матриц слишком большие, то начинает сказываться эффект охлаждения кеша, поэтому, в реальности граница подбирается экспериментально, т.е. большое изображение может быть обработано участками.
Отредактировано 24.10.2020 16:15 vdimas . Предыдущая версия . Еще …
Отредактировано 24.10.2020 16:14 vdimas . Предыдущая версия .
Отредактировано 24.10.2020 16:13 vdimas . Предыдущая версия .
Re[3]: Производительность .Net на вычислительных задачах
От: Evgeny.Panasyuk Россия  
Дата: 24.10.20 16:50
Оценка: 2 (1)
Здравствуйте, Sinclair, Вы писали:

S>Хочется-то иметь компактный, понятный, легко отлаживаемый пользовательский код. А уж разворачиванием его в килобайты бинаря под конкретную архитектуру пусть занимается тупая машина.

S>Может быть, и на С++ тоже можно написать какой-то подобный аналог.

Кончено — есть compile-time EDSL'и — например expresion templates, на них например построен Eigen — пользовательский высокоуровневый код работающий с матрицами, оптимизируется библиотекой на этапе компиляции, причём на разных уровнях, начиная с того как эффективней расставить скобки в выражении, и заканчивая разкруткой циклов, параллелизацей и векторизацией под конкретную платформу.
Эта техника вообще старше C#: статья из 1995. И даже есть не просто библиотеки использующие expression templates, а есть библиотеки для построения таких библиотек:

https://www.boost.org/doc/libs/1_74_0/doc/html/proto.html
In short, Proto is an EDSL for defining EDSLs.


Также есть варианты построения runtime-EDSL'ей, они ещё более выразительные, в бэкенде дёргается обычный компилятор, здесь возможности вообще безграничны, вплоть до оптимизации кода под конкретные runtime данные. Это всё например реализовано в библиотеке TaskGraph.
Собственно ты это и делаешь — самописная трансляция из EDSL'я в векторные инструкции. Только для этого Linq не нужен — runtime дерево выражений строится практически на любом языке на минимальном наборе языковых фич — смотри например SymPy. Если бы ты сам себя не ограничивал Linq'ом — получилось намного более выразительней и мощнее
Отредактировано 24.10.2020 18:19 Evgeny.Panasyuk . Предыдущая версия .
Re[5]: Производительность .Net на вычислительных задачах
От: Evgeny.Panasyuk Россия  
Дата: 24.10.20 17:13
Оценка: 2 (1) +1
Здравствуйте, Sinclair, Вы писали:

KP>>В тезисе доклада указано, что linq2d будет использовать SIMD-инструкции, так что если авторы библиотеки вручную доработали генерацию кода для таких случаев, то результат ожидаемый. Плюсовый код же намекает на большое количество промахов по кэшу, да ассемблерный выхлоп вроде не изобилует векторизацией.

S>Кстати, вполне себе изобилует. Смотрите — все эти xmm0/xmm1/xmm2 это же SSE.

Векторизация != использованию xmm регистров.
На x64 компиляторы их используют для обычных не векторизированных операций. И они даже являются частью calling convention.
Например:
double mul(double x, double y)
{
    return x * y;
}

Выдаёт:
mul(double, double):
        mulsd   xmm0, xmm1
        ret

Тут ключевое это какая именно инструкция, а не регистр. mulsd — скалярная, не векторнная. Векторный аналог — mulpd.
Re[2]: Производительность .Net на вычислительных задачах
От: Evgeny.Panasyuk Россия  
Дата: 25.10.20 03:51
Оценка: 2 (1)
Здравствуйте, AeroSun, Вы писали:

AS>В тему не вникал, но все холивары о том, что *ЛюбойЯзык* производительнее С++ (даже с тестами и красивыми графиками) всегда разбиваются о два вопроса:

AS>1) За счёт чего они быстрее С++?

За счёт того что он написал свой оптимизирующий транслятор из mini-EDSL в avx. С ещё большим успехом мог бы просто выплёвывать OpenCL код. У OpenCL уже есть бэкенды для GPU, AVX/etc, multi-thread.

AS>2) Почему это не является достижимым на С++?


Runtime транслятор можно написать на чём угодно, хоть на Python'е — что например и делает TensorFlow.
У C++ тут дополнительный бонус в том, что такой транслятор можно сделать и run-time и compile-time (где не будет необходимости в кэшировании сгенерированного кода, так он он уже будет в бинарнике после компиляции (но тут свои минусы есть)).
Отредактировано 25.10.2020 3:56 Evgeny.Panasyuk . Предыдущая версия . Еще …
Отредактировано 25.10.2020 3:55 Evgeny.Panasyuk . Предыдущая версия .
Re[8]: Производительность .Net на вычислительных задачах
От: Sinclair Россия https://github.com/evilguest/
Дата: 25.10.20 07:17
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Соотв., векторизация — это использование векторных команд, например из семейства PCLMUL.

Просто непонятно, что такое использование файла регистров ymm, но без векторизации. С ними же работают simd инструкции.
Всё, я понял, ткнули носом.
Я просто вообще был не в курсе про существование scalar инструкций с векторными регистрами.

V>Т.е., в чём тут фишка... Иногда под векторные операции выгодно выделять доп. промежуточную память, например, если эта выделенная память позволит применять векторные инструкции по прямому назначению.

Не очень понятно, каким образом лишняя память тут сможет помочь. Инструкций память-память в интеловском SIMD вроде бы нету.
А если я загрузил данные в регистр, то мне надо не переливать их в дополнительную память, а считать математику.
V>Т.е., порой быстрее будет не поэлементно делать все вычисления, а за раз сделать один тип вычислений над массивом данных, затем другой тип вычислений над промежуточным результатом и т.д., т.е. от поэлементных вычислений уходить в матричные/векторные.
Возможно, но пока что я себе такую ситуацию представить не могу — ну, в том случае, если у нас поэлементное вычисление в принципе возможно. В sauvola — да, там есть отдельный этап предварительного интегрирования, который плохо совмещается с вычислением порога. Ну, то есть известно, что при расчёте порога для [i, j] нам не надо заглядывать в интегралы дальше, чем [i+halfW, j+halfW], поэтому теоретически можно всё засунуть в один цикл, но вряд ли с этого будет как-то особенно много толку.

А вот если у нас есть какая-то поэлементно работающая формула, то я не понимаю, при каких условиях сохранение промежуточного результата даст хоть какой-то эффект.
Ну, вот вычисляем мы из трёх массивов поэлементно r = a + b * c.
Можно, конечно, сначала рассчитать t = b * c при помощи векторных инструкций, сложить его в память.
Потом векторно рассчитывать r = a + t при помощи векторных инструкций, выкинуть t.
Но это же гарантированно просадит производительность, потому что в "тупом" варианте у нас результат b * c уже лежит в регистре, и его нужно только прибавить к a, и в память сложить сразу результат.
То есть мы сначала из
load(b)
load(c)
t=mul(b, c)
load(a)
r=add(t, a)
save(r)

искусственно делаем
load(b)
load(c)
t=mul(b, c)
save(t)
load(t)
 load(a)
r=add(t, a)
save(r)

, а потом ещё и выносим load(t)/load(a) в другой цикл, чтобы избежать кэширования

А в реальном SIMD есть ещё и комбинированные инструкции ADDMUL, которые вообще позволяют вычислить r в один приём.

Короче, нужны примеры кода, в которых сохранение промежуточного результата в память что-то даст.

V>Понятно, что если размеры векторов/матриц слишком большие, то начинает сказываться эффект охлаждения кеша, поэтому, в реальности граница подбирается экспериментально, т.е. большое изображение может быть обработано участками.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 25.10.2020 8:04 Sinclair . Предыдущая версия .
Re[6]: Производительность .Net на вычислительных задачах
От: Sinclair Россия https://github.com/evilguest/
Дата: 25.10.20 08:03
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>
EP>mul(double, double):
EP>        mulsd   xmm0, xmm1
EP>        ret
EP>

EP>Тут ключевое это какая именно инструкция, а не регистр. mulsd — скалярная, не векторнная. Векторный аналог — mulpd.
Всё, я понял. Невнимательно посмотрел. Да, конечно, все эти mulsd/addsd, которых компилятор там понафигачил, вполне себе скалярны.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[9]: Производительность .Net на вычислительных задачах
От: vdimas Россия  
Дата: 25.10.20 19:25
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Т.е., в чём тут фишка... Иногда под векторные операции выгодно выделять доп. промежуточную память, например, если эта выделенная память позволит применять векторные инструкции по прямому назначению.

S>Не очень понятно, каким образом лишняя память тут сможет помочь. Инструкций память-память в интеловском SIMD вроде бы нету.

Для соотв. векторной загрузки/выгрузки.
Т.е. форматы исходных и конечных данных не обязательно хорошо ложатся на векторные загрузку/выгрузку, но промежуточные результаты вычислений могут ложиться хорошо.

В любом случае зависит от конкретных вычислений. Например, при многостадийной обработке стерео-аудио (поток пар float32) выгодно первую операцию (из конвейера их) спланировать так, чтобы разделить каналы в промежуточных данных, дальнейшая обработка этих данных по незавсимым каналам будет происходить максимально эффективно, последняя операция должна опять собирать два потока в один. Плюс к этому, исходные и конечные данные могут представлять из себя стрим float32, а вычисления имеет смысл делать во float64, это касается и промежуточных результатов.


S>А если я загрузил данные в регистр, то мне надо не переливать их в дополнительную память, а считать математику.


Для вычислений с плавающей точкой есть еще одна характеристика (в сравнении с целочисленными вычислениями) — это трафик подгрузки данных, т.е. эффективность такой операции. В плавающей точке даже константы не идут как аргументы операции, они идут как обычные внешние данные.


V>>Т.е., порой быстрее будет не поэлементно делать все вычисления, а за раз сделать один тип вычислений над массивом данных, затем другой тип вычислений над промежуточным результатом и т.д., т.е. от поэлементных вычислений уходить в матричные/векторные.

S>Возможно, но пока что я себе такую ситуацию представить не могу — ну, в том случае, если у нас поэлементное вычисление в принципе возможно. В sauvola — да, там есть отдельный этап предварительного интегрирования, который плохо совмещается с вычислением порога.

Конкретно в sauvola я внимательно не втыкал, да и смысл втыкать конкретно в него? Речь же идёт о произвольном алгоритме?

Причём, твой подход может быть интересен и для 1D, потому что в рекурсивной фильтрации, например, на каждом шаге вычисляются элементы x0*a0, x1*a1, .., y0*b0, y(-1)*b1, ...
Т.е., возможность относительной адресации — она полезна не только для 2D алгоритмов.

Так шта, вот тебе идея — допилить свою реализацию до 1D.
Я с фильтрацией периодически вожусь и погонял бы её и на этот счёт.
Потому что, несколько раз подходил к дотнету ранее, но он не использует векторные инструкции при компиляции вычислений с плавающей точкой (ранее точно не использовал, я проверял раз в несколько лет), поэтому я так и не занимался обработкой звука всерьёз на C#.

Но если ты уже так далеко продвинулся в генерации векторных вычислений на C#, в оптимизации доступа к массивам и прочим таким вещам — то тебе там будет сделать совсем немного.

И что-то мне подсказывает, что эту работу можно будет слать разработчикам дотнета... Ну или оформлять в виде общедоступного nuget-пакета.
Разумеется, там еще проверять это всё на прочность и прочую дуракоустойчивость... возможно, еще допиливать, но если уже столько труда вложено — имеет смысл не бросать эту разработку.


S>а потом ещё и выносим load(t)/load(a) в другой цикл, чтобы избежать кэширования


Угу.
Для 1d там же потребуется "прокрутить" данные в регистрах, т.е. сдвинуть их на один отсчёт.

А ты не экспериментировал еще с имеющимися соотв. ср-вами дотнета, там же есть векторные операции:
https://docs.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector256?view=netcore-3.1
Что оно порождает?
Т.е. что насчёт детектирования текущей железки и порожения кода под неё конкретную?


S>А в реальном SIMD есть ещё и комбинированные инструкции ADDMUL, которые вообще позволяют вычислить r в один приём.


Ну да, для фильтрации, корелляции, свёртки и ИИ основная операция — поэлементное умножение векторов с накоплением суммы, S=x0*a0+x1*a1+...


S>Короче, нужны примеры кода, в которых сохранение промежуточного результата в память что-то даст.


Поток пар отсчётов стерео {float32, float32}.
(один из самых популярных "сырых" звуковых форматов на сегодня, т.е. то, что дают кодеки, в каком формате происходит захват и воспроизведение аудио из/в железо в современных аудио-АПИ)
Re[10]: Производительность .Net на вычислительных задачах
От: Sinclair Россия https://github.com/evilguest/
Дата: 26.10.20 05:04
Оценка:
Здравствуйте, vdimas, Вы писали:

V>В любом случае зависит от конкретных вычислений. Например, при многостадийной обработке стерео-аудио (поток пар float32) выгодно первую операцию (из конвейера их) спланировать так, чтобы разделить каналы в промежуточных данных, дальнейшая обработка этих данных по незавсимым каналам будет происходить максимально эффективно, последняя операция должна опять собирать два потока в один.

Эмм, это если у нас разные формулы для левого и правого каналов, да? А зачем такое бывает нужно?
V>Плюс к этому, исходные и конечные данные могут представлять из себя стрим float32, а вычисления имеет смысл делать во float64, это касается и промежуточных результатов.
Эмм, VCVTPS2PD / VCVTDQ2PD?

V>Для вычислений с плавающей точкой есть еще одна характеристика (в сравнении с целочисленными вычислениями) — это трафик подгрузки данных, т.е. эффективность такой операции. В плавающей точке даже константы не идут как аргументы операции, они идут как обычные внешние данные.

Константы грузятся в регистр 1 (один) раз, после чего используются в цикле. Какой смысл тягать их из памяти на каждой итерации?

V>Конкретно в sauvola я внимательно не втыкал, да и смысл втыкать конкретно в него? Речь же идёт о произвольном алгоритме?

Ну вот я и не могу себе представить такой алгоритм.
V>Причём, твой подход может быть интересен и для 1D, потому что в рекурсивной фильтрации, например, на каждом шаге вычисляются элементы x0*a0, x1*a1, .., y0*b0, y(-1)*b1, ...
V>Т.е., возможность относительной адресации — она полезна не только для 2D алгоритмов.
Ну да. Там даже проще всё. Но всё ещё не вижу смысла складывать что-то в промежуточную память.

V>Так шта, вот тебе идея — допилить свою реализацию до 1D.

Да, на конференции задавали такой вопрос.
V>Я с фильтрацией периодически вожусь и погонял бы её и на этот счёт.
V>Потому что, несколько раз подходил к дотнету ранее, но он не использует векторные инструкции при компиляции вычислений с плавающей точкой (ранее точно не использовал, я проверял раз в несколько лет), поэтому я так и не занимался обработкой звука всерьёз на C#.
Да, автовекторизации как не было, так и нет.
V>Но если ты уже так далеко продвинулся в генерации векторных вычислений на C#, в оптимизации доступа к массивам и прочим таким вещам — то тебе там будет сделать совсем немного.
Я хочу сначала довести linq2d до более-менее приемлемого состояния, чтобы не стыдно было хотя бы пакет выложить.
Пока что я этого не делаю, т.к. есть риск изменений, ломающих обратную совместимость.

Ну, и скорость тоже — не сделано как минимум две вещи:
1. Разворачивание циклов. На первый взгляд, ICC-шный код отличается как раз тем, что он "бежит" не одним SIMD-регистром, а сразу двумя. Да ещё и перемешивает операции загрузки с операциями вычисления.
2. Оптимизация рекуррентных обращений. Как раз то, что потребуется для 1d.
V>И что-то мне подсказывает, что эту работу можно будет слать разработчикам дотнета... Ну или оформлять в виде общедоступного nuget-пакета.
V>Разумеется, там еще проверять это всё на прочность и прочую дуракоустойчивость... возможно, еще допиливать, но если уже столько труда вложено — имеет смысл не бросать эту разработку.
Да тут не так много усилий — немножко по вечерам.

S>>а потом ещё и выносим load(t)/load(a) в другой цикл, чтобы избежать кэширования

V>Угу.
V>Для 1d там же потребуется "прокрутить" данные в регистрах, т.е. сдвинуть их на один отсчёт.
Да, в том-то и дело. В 2d, например, обращение к готовому результату "на строчку выше" гарантированно безопасно, т.к. там расчёт уже закончен. А в 1d безопасны только обращения на расстояние, больше, чем длина регистра.
А вот обращения типа r[-1] надо прокручивать. Как раз эта штука у меня не сделана.

V>А ты не экспериментировал еще с имеющимися соотв. ср-вами дотнета, там же есть векторные операции:

V>https://docs.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector256?view=netcore-3.1
V>Что оно порождает?
Так я его и использую. У меня же нет прямого доступа к бинарю — я порождаю MSIL, сдобренный интринсиками. А дальше — уже JIT.
V>Т.е. что насчёт детектирования текущей железки и порожения кода под неё конкретную?
Детектирование делается очень тупо незатейливо. При инициализации, библиотека строит список векторных операций. Например, https://github.com/evilguest/linq2d/blob/master/Linq2d/CodeGen/VectorData.cs#L291-L465
В зависимости от того, какие фичи обнаружены в рантайме, добавляются разные операции.

А потом, при компиляции ядра фильтра, мы просто идём вверх по дереву и смотрим, можно ли векторизовать скалярные операции.
Причём пока что сделано очень, очень тупо — из всех композитных операций рассматриваются только загрузка-с-конвертацией.
Например, если мы, допустим, складываем short и int, то в AST у нас лежит Add(Convert(Load(s), typeof(int)), Load(i)).
Когда векторизатор анализирует это выражение, он видит "о, я могу загрузить вектор из 16 шортов, ура", но "ой, загрузить вектор из 16 интов мне некуда".
А вот дальше он смотрит на 8-элементные вектора, а там есть LoadAndConvert из short* в Vector256<int>. Отлично, у нас уже есть готовый Vector256<int>, смотрим второй аргумент — тоже есть конверсия из int* в Vector2556<int>.
Едем дальше — есть ли операция для сложения таких векторов? Есть. Всё, успех — у нас готово векторное ядро:
Avx2.Store(
  result+x, 
  Avx2.Add(
    Avx2.LoadVector256Int32(s+x), 
    Avx2.LoadVector256(i+x))
  )


V>Ну да, для фильтрации, корелляции, свёртки и ИИ основная операция — поэлементное умножение векторов с накоплением суммы, S=x0*a0+x1*a1+...

S>>Короче, нужны примеры кода, в которых сохранение промежуточного результата в память что-то даст.
V>Поток пар отсчётов стерео {float32, float32}.
V>(один из самых популярных "сырых" звуковых форматов на сегодня, т.е. то, что дают кодеки, в каком формате происходит захват и воспроизведение аудио из/в железо в современных аудио-АПИ)
И что с ним нужно сделать?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 26.10.2020 5:11 Sinclair . Предыдущая версия .
Re[11]: Производительность .Net на вычислительных задачах
От: Danchik Украина  
Дата: 26.10.20 07:47
Оценка:
Здравствуйте, Sinclair, Вы писали:

[Skip]

S>Детектирование делается очень тупо незатейливо. При инициализации, библиотека строит список векторных операций. Например, https://github.com/evilguest/linq2d/blob/master/Linq2d/CodeGen/VectorData.cs#L291-L465

S>В зависимости от того, какие фичи обнаружены в рантайме, добавляются разные операции.

Я профан в интрсинках, просто в глаза бросается отсутствие else. Так задумано?
Re[12]: Производительность .Net на вычислительных задачах
От: Sinclair Россия https://github.com/evilguest/
Дата: 26.10.20 12:36
Оценка:
Здравствуйте, Danchik, Вы писали:
D>Я профан в интрсинках, просто в глаза бросается отсутствие else. Так задумано?
Да, конечно. В случае else у нас просто провал векторизации.
Векторизация сделана, повторюсь, достаточно тупо. Она проверяет возможность выполнить всё вычисление ядра одной длиной вектора (в терминах элементов).
И для поиска подходящего размера вектора перебирает всё начиная с максимального — то есть смотрим 32 элемента; 16; 8; 4; 2.
Скажем, если мы складываем int, int, и double, то первую половину можно сделать по 8 элементов за раз. Но тогда мы упрёмся в то, что нет операции "загрузить 8 double в регистр", поэтому 8 не подходит.
Для векторов размером 4 есть возможность зачитать Vector128<int> (SSE) и сложить их (SSE2). Если нету AVX и выше, то на этом всё, и 4 как длина вектора тоже не подойдёт.
А вот если есть AVX, то можно загрузить Vector256<double>, а также сконвертировать результат целого сложения в double, что даёт нам успех на длине вектора = 4.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[11]: Производительность .Net на вычислительных задачах
От: vdimas Россия  
Дата: 26.10.20 15:34
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>В любом случае зависит от конкретных вычислений. Например, при многостадийной обработке стерео-аудио (поток пар float32) выгодно первую операцию (из конвейера их) спланировать так, чтобы разделить каналы в промежуточных данных, дальнейшая обработка этих данных по незавсимым каналам будет происходить максимально эффективно, последняя операция должна опять собирать два потока в один.

S>Эмм, это если у нас разные формулы для левого и правого каналов, да? А зачем такое бывает нужно?

Формулы одинаковые для каждого канала, просто в памяти эффективней расположить подряд данные от одного канала, а не "смесь" их.


V>>Для вычислений с плавающей точкой есть еще одна характеристика (в сравнении с целочисленными вычислениями) — это трафик подгрузки данных, т.е. эффективность такой операции. В плавающей точке даже константы не идут как аргументы операции, они идут как обычные внешние данные.

S>Константы грузятся в регистр 1 (один) раз, после чего используются в цикле. Какой смысл тягать их из памяти на каждой итерации?

Зависит от порядка фильтра.
Если фильтр большого порядка, или нерекурсивный, или это свёртка, тут возможны варианты, смотря чего больше — членов многочлена или отсчётов в пачке (данные идёт пачками по 80, 160, 240, 360 отсчётов).
Т.е. по чему будет итерация внешнего цикла, а по чему — внутреннего.


V>>Конкретно в sauvola я внимательно не втыкал, да и смысл втыкать конкретно в него? Речь же идёт о произвольном алгоритме?

S>Ну вот я и не могу себе представить такой алгоритм.

Да любой, где у нас RGBA-цвет.
Компоненты цвета в процессе вычислений преобразоуются, затем надо обратно упаковать полученные результаты с двусторонней обрезкой в RGBA.
(вроде бы, есть инструкции в SIMD, которые эффективно выполняют обрезку сигнала)


S>Я хочу сначала довести linq2d до более-менее приемлемого состояния, чтобы не стыдно было хотя бы пакет выложить.


+1


S>Ну, и скорость тоже — не сделано как минимум две вещи:

S>1. Разворачивание циклов.

Этим стоит управлять, ИМХО (т.е. дать такую возможность).
Чтобы можно было задать грануляцию такого разворачивания.
Потому что оно тоже зависит от алгоритма.


S>На первый взгляд, ICC-шный код отличается как раз тем, что он "бежит" не одним SIMD-регистром, а сразу двумя. Да ещё и перемешивает операции загрузки с операциями вычисления.


"Перемешивать" современный процессора сам должен.
С другой стороны, если данные зависимы, то сам он ничего не перемешает; может, для этого гонятся 2 регистра, чтобы явно оперировать независимыми данными.


V>>Для 1d там же потребуется "прокрутить" данные в регистрах, т.е. сдвинуть их на один отсчёт.

S>Да, в том-то и дело. В 2d, например, обращение к готовому результату "на строчку выше" гарантированно безопасно, т.к. там расчёт уже закончен. А в 1d безопасны только обращения на расстояние, больше, чем длина регистра.

Но для рекурсивных ф-ий из сигнальщины требуются y0, y-1, y-2 и т.д.
где yi = f(xi-n, ..., xi, yi-n, ..., yi-1)


V>>А ты не экспериментировал еще с имеющимися соотв. ср-вами дотнета, там же есть векторные операции:

V>>https://docs.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector256?view=netcore-3.1
V>>Что оно порождает?
S>Так я его и использую. У меня же нет прямого доступа к бинарю

С утра на свежую голову вспомнил про этот заданный мной вопрос и понял, что он глупый. ))


S>из всех композитных операций рассматриваются только загрузка-с-конвертацией.


Тогда еще есть куда расти.

Практически всегда в подобных спорах я с тобой согласен насчёт рассуждений "как бы это могло быть, если бы по-уму".
Расхождения обычно в понимании проблем текущий реалий.
А они таковы, что современные компиляторы не в состоянии порождать хороший векторный код из-за ограничений используемых инструкций.
Т.е., в исходнике надо тщательно планировать эту векторизацию, чтобы имеющиеся инструкции хорошо на неё ложились.

Именно поэтому толку от векторных инструкций в "обычном коде" пока не слишком много, кое-какое ускорение чаще достигается, (уже не раз обращал внимание), за счёт большего файла регистров.

От SIMD-инструкций есть пока хороший толк в специально-разработанных и отлаженных кодеках, т.е. в системном ПО/драйверах.
Но там всё ручками вылизано.
Как и в интелловской SIMD-либе, ссылки на которую давал — там тоже ручками вылизано.
Т.е. вот таких размеров ж-па происходит в этой реальности...

Вот и охота, таки, увидеть при жизни тот обещанный 18 лет назад (какой кошмар!) подход, что мы "обычным образом" расписали алгоритм, и "оно само" породило из него достойный код. ))


S>>>Короче, нужны примеры кода, в которых сохранение промежуточного результата в память что-то даст.

V>>Поток пар отсчётов стерео {float32, float32}.
V>>(один из самых популярных "сырых" звуковых форматов на сегодня, т.е. то, что дают кодеки, в каком формате происходит захват и воспроизведение аудио из/в железо в современных аудио-АПИ)
S>И что с ним нужно сделать?

Натравливать на них линейные и нелинейный фильтры и всевозможные другие алгоритмы.

Причём, вряд ли ошибусь, если предположу, что 1D в разы более востребовано, чем 2D.
Потому что 2D — это на сегодня относительно узкая прикладная область, в отличие от перечисленных областей 1D.
Re[12]: Производительность .Net на вычислительных задачах
От: Sinclair Россия https://github.com/evilguest/
Дата: 26.10.20 16:24
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Формулы одинаковые для каждого канала, просто в памяти эффективней расположить подряд данные от одного канала, а не "смесь" их.

По-прежнему не вижу обоснования этой эффективности.
Например, если мне нужно умножить оба канала на 0.74, то я просто
1. Загружаю 0.74 в регистр ymm0
2. Читаю данные блоками по 8 single, оба канала сразу
3. Выполняю умножение на ymm0
4. Пишу в память готовый результат.
5. Перехожу к п.2.

Разделение на два канала — удвоение memory traffic на ровном месте.

V>Т.е. по чему будет итерация внешнего цикла, а по чему — внутреннего.

Со свёрткой — может быть. Надо смотреть.

V>Да любой, где у нас RGBA-цвет.

V>Компоненты цвета в процессе вычислений преобразоуются, затем надо обратно упаковать полученные результаты с двусторонней обрезкой в RGBA.
Да, с цветами я ещё не смотрел.
V>(вроде бы, есть инструкции в SIMD, которые эффективно выполняют обрезку сигнала)
Да, там есть saturated addition.
Пока что идея — в том, чтобы сделать свои структуры над Vector256<byte>, типа Vector256Color. (К сожалению, нельзя делать настоящий Vector256<> от пользовательских типов. Задекларировать можно, но в рантайме это упадёт при джите.)
А операции над ними реализовать в виде ручных методов над Vector256<byte>.

Вот, я тут экспериментировал с Complex:
https://github.com/evilguest/linq2d/blob/master/Linq2d/Complex/ComplexVector.cs

V>Этим стоит управлять, ИМХО (т.е. дать такую возможность).

V>Чтобы можно было задать грануляцию такого разворачивания.
V>Потому что оно тоже зависит от алгоритма.
Ну, ситуаций, чтобы разворачивание цикла сделало прямо хуже — вряд ли. В худшем случае просто не будет выигрыша.

V>С другой стороны, если данные зависимы, то сам он ничего не перемешает; может, для этого гонятся 2 регистра, чтобы явно оперировать независимыми данными.

Ну, что точно — так это то, что у авторов ICC максимальный доступ к тому, как там внутри всё шедулится

V>>>Для 1d там же потребуется "прокрутить" данные в регистрах, т.е. сдвинуть их на один отсчёт.

S>>Да, в том-то и дело. В 2d, например, обращение к готовому результату "на строчку выше" гарантированно безопасно, т.к. там расчёт уже закончен. А в 1d безопасны только обращения на расстояние, больше, чем длина регистра.

V>Но для рекурсивных ф-ий из сигнальщины требуются y0, y-1, y-2 и т.д.

V>где yi = f(xi-n, ..., xi, yi-n, ..., yi-1)
Да, я в курсе. Там есть несколько подходов к векторизации; как ты верно заметил — смотря какой цикл делать внутренним.
В linq2d пока нет заточки на свёртку; если я захочу сделать свёртку с y0,-1, y0,-2, y0,-3 то мне так и надо будет написать a*y[0,-1] + b*y[0,-2] + c*y[0,-3].
А не r[0]*{a, b, c} или что-то типа того.

V>Тогда еще есть куда расти.

Отож.

V>Натравливать на них линейные и нелинейный фильтры и всевозможные другие алгоритмы.

Ну, как всегда, надо начинать с примеров задач
V>Причём, вряд ли ошибусь, если предположу, что 1D в разы более востребовано, чем 2D.
V>Потому что 2D — это на сегодня относительно узкая прикладная область, в отличие от перечисленных областей 1D.
Наверняка. С другой стороны, там может уже всё и реализовано, что можно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[13]: Производительность .Net на вычислительных задачах
От: vdimas Россия  
Дата: 27.10.20 05:46
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Разделение на два канала — удвоение memory traffic на ровном месте.


Если промежуточные результаты в регистрах сохранить не получится (а в моих экспериментах не получится с запасом раз так в 50-100), то трафик с памятью будет в любом случае.


V>>Т.е. по чему будет итерация внешнего цикла, а по чему — внутреннего.

S>Со свёрткой — может быть. Надо смотреть.

Нерекурсивный фильтр выполняет те же операции.
Собсно, свёртка с импульсным откликом (IR) — одна из разновидностей нерекурсивного фильтра.
Причём, самая мощная из разновидностей, бо способна на произвольные фазовые задержки, т.е. позволяет даже описывать реверберацию и эхо.


S>Пока что идея — в том, чтобы сделать свои структуры над Vector256<byte>, типа Vector256Color. (К сожалению, нельзя делать настоящий Vector256<> от пользовательских типов. Задекларировать можно, но в рантайме это упадёт при джите.)


Да пофик.
MemoryMarshal.CreateReadOnlySpan<T>(..) и зеркальный ему MemoryMarshal.AsRef<T>(..) — оба имеют нулевую стоимость в рантайм, это просто unsafe-реинтерпретация памяти.

Или без Span<>, просто ссылки:
public static class reinterpret_cast<TDst> {
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ref TDst from<TSrc>(ref TSrc src) => ref Unsafe.As<TSrc, TDst>(ref src);
}

public static class const_reinterpret_cast<T> {
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ref readonly T from<TSrc>(in TSrc src) => ref Unsafe.As<TSrc, T>(ref Unsafe.AsRef(in src));
}



V>>Этим стоит управлять, ИМХО (т.е. дать такую возможность).

V>>Чтобы можно было задать грануляцию такого разворачивания.
V>>Потому что оно тоже зависит от алгоритма.
S>Ну, ситуаций, чтобы разворачивание цикла сделало прямо хуже — вряд ли. В худшем случае просто не будет выигрыша.

В худшем случае будет хуже, чем в лучшем.

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


V>>С другой стороны, если данные зависимы, то сам он ничего не перемешает; может, для этого гонятся 2 регистра, чтобы явно оперировать независимыми данными.

S>Ну, что точно — так это то, что у авторов ICC максимальный доступ к тому, как там внутри всё шедулится

Спецификации в плане "зависимости" данных для процессоров Intel публично доступны и вряд ли есть дополнительные тайные знания.
Включая здравый смысл, Intel не выгодно скрывать от общественности ту информацию, которая поможет скомпиллировать под их процессоры наилучший код.


V>>Но для рекурсивных ф-ий из сигнальщины требуются y0, y-1, y-2 и т.д.

V>>где yi = f(xi-n, ..., xi, yi-n, ..., yi-1)
S>Да, я в курсе.

"Я в курсе" не согласуется с написанным тобой выше:

V>>Формулы одинаковые для каждого канала, просто в памяти эффективней расположить подряд данные от одного канала, а не "смесь" их.
S>По-прежнему не вижу обоснования этой эффективности.
S>Например, если мне нужно умножить оба канала на 0.74, то я просто




V>>Натравливать на них линейные и нелинейный фильтры и всевозможные другие алгоритмы.

S>Ну, как всегда, надо начинать с примеров задач

Например, подобного рода баловство:
http://files.rsdn.org/21096/MusicFX.png


V>>Причём, вряд ли ошибусь, если предположу, что 1D в разы более востребовано, чем 2D.

V>>Потому что 2D — это на сегодня относительно узкая прикладная область, в отличие от перечисленных областей 1D.
S>Наверняка. С другой стороны, там может уже всё и реализовано, что можно.

Для дотнета ничего вменяемого нет.
Поэтому, оффлайн-вычисления крутят на Питоне, а реалтаймовые на плюсах.

Если ты вплотную к плюсам подобрался, то представления о том, как должны выглядеть современные эксперименты, скажем, в области ИИ, могут малость поменяться. ))
Отредактировано 27.10.2020 5:48 vdimas . Предыдущая версия .
Re[14]: Производительность .Net на вычислительных задачах
От: Sinclair Россия https://github.com/evilguest/
Дата: 27.10.20 14:39
Оценка:
Здравствуйте, vdimas, Вы писали:
V>Если промежуточные результаты в регистрах сохранить не получится (а в моих экспериментах не получится с запасом раз так в 50-100), то трафик с памятью будет в любом случае.
Интересно посмотреть на формулы, в которых это так устроено. Откуда берутся промежуточные результаты размером в 100 регистр файлов?

V>Причём, самая мощная из разновидностей, бо способна на произвольные фазовые задержки, т.е. позволяет даже описывать реверберацию и эхо.

Хм, реверберация — она же только с рекурсивным фильтром.
Я так понимаю, что под "самой мощной" имеется в виду "среди линейных фильтров", так? С нелинейщиной всё может быть гораздо кучерявее.

V>Да пофик.

V>MemoryMarshal.CreateReadOnlySpan<T>(..) и зеркальный ему MemoryMarshal.AsRef<T>(..) — оба имеют нулевую стоимость в рантайм, это просто unsafe-реинтерпретация памяти.

Ну, так-то пофиг, но работать с этим неудобно. Это ж надо весь этот код по приведению Сolor* к Vector256<Color> порождать в MSIL.

V>При слишком большом N разворачивания цикла получается многократное N-дублирование тела цикла.

V>И чем больше в бинаре подобного кода, тем быстрее охлаждается кеш.
V>Всегда есть некий оптимум этого N.
А зачем слишком большой? При работе с SIMD больше чем 2-4 раза разворачивать и не приходится. Там всё равно всё быстро упирается в быстродействие памяти.
V>Включая здравый смысл, Intel не выгодно скрывать от общественности ту информацию, которая поможет скомпиллировать под их процессоры наилучший код.
Я имею в виду, что вряд ли бы они стали перемешивать код просто так, не имея к этому соображений.

V>"Я в курсе" не согласуется с написанным тобой выше:

V>

V>>>Формулы одинаковые для каждого канала, просто в памяти эффективней расположить подряд данные от одного канала, а не "смесь" их.
S>>По-прежнему не вижу обоснования этой эффективности.
S>>Например, если мне нужно умножить оба канала на 0.74, то я просто

Вполне согласуется. Если формула одинаковая, то она будет записываться в виде SIMD хоть для отдельных каналов, хоть для пары каналов через один.
Та же свёртка просто будет не с вектором (a0, a1, a2, a3), а c вектором (a0, a0, a1, a1).
Кстати, каков типичный размер IR для практических фильтров?

V>Например, подобного рода баловство:

V>http://files.rsdn.org/21096/MusicFX.png
Ну, так это ж картинка. Надо смотреть внутрь — как там эти фильтры представлены. И можно ли представить их лучше/удобнее, чем они уже представлены.

V>Для дотнета ничего вменяемого нет.

Ну, так это типичная catch-22: перформанс — отстой, поэтому никто ничего не делает. Поэтому перформанс — отстой.
V>Поэтому, оффлайн-вычисления крутят на Питоне, а реалтаймовые на плюсах.

V>Если ты вплотную к плюсам подобрался, то представления о том, как должны выглядеть современные эксперименты, скажем, в области ИИ, могут малость поменяться. ))
Мои представления изменить нетрудно — я об этом знаю примерно ноль.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[15]: Производительность .Net на вычислительных задачах
От: vdimas Россия  
Дата: 27.10.20 15:38
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Если промежуточные результаты в регистрах сохранить не получится (а в моих экспериментах не получится с запасом раз так в 50-100), то трафик с памятью будет в любом случае.

S>Интересно посмотреть на формулы, в которых это так устроено. Откуда берутся промежуточные результаты размером в 100 регистр файлов?

Стек аудио-эффектов для гитары, например.


V>>Причём, самая мощная из разновидностей, бо способна на произвольные фазовые задержки, т.е. позволяет даже описывать реверберацию и эхо.

S>Хм, реверберация — она же только с рекурсивным фильтром.

Рекурсивная при эмуляции в цифре аналоговых процессов.
Но "родной" цифровой способ (т.е. позволяющий получить наибольшую точность) — через свёртку:
https://en.wikipedia.org/wiki/Convolution_reverb

Т.е., даже если импульсная характеристика порождена не аналоговым процессом, а цифровым, то делается это примерно так:
— на некоей модели, оперирующей большой точностью, моделируется некая обработка сигнала;
— с этой модели снимается итоговая импульсная характеристика;
— в реалтайме затем происходит свёртка входного сигнала с импульсной характеристикой.

Суть в том, что "точная модель" оперирует большой разрядностью и кратной передискетизацией унутре, поэтому эта модель не работает в реалтайм, она используется один раз при вычислении IR.


S>Я так понимаю, что под "самой мощной" имеется в виду "среди линейных фильтров", так? С нелинейщиной всё может быть гораздо кучерявее.


Нелинейщина обычно проще. Это разного рода пороговые ф-ии, типа sqrt, log и т.д. и они часто реализованы через табличную аппроксимацию, т.е. практически бесплатны.


V>>MemoryMarshal.CreateReadOnlySpan<T>(..) и зеркальный ему MemoryMarshal.AsRef<T>(..) — оба имеют нулевую стоимость в рантайм, это просто unsafe-реинтерпретация памяти.

S>Ну, так-то пофиг, но работать с этим неудобно. Это ж надо весь этот код по приведению Сolor* к Vector256<Color> порождать в MSIL.

Ты спросил "как" — я показал направление.


V>>При слишком большом N разворачивания цикла получается многократное N-дублирование тела цикла.

V>>И чем больше в бинаре подобного кода, тем быстрее охлаждается кеш.
V>>Всегда есть некий оптимум этого N.
S>А зачем слишком большой? При работе с SIMD больше чем 2-4 раза разворачивать и не приходится.

Компилятор Intel С++ разворачивает циклы именно при работе с SIMD в N=16, 32, ..., 128 раз.


S>Там всё равно всё быстро упирается в быстродействие памяти.


Кеш данных 1-го уровня имеет в точности быстродействие файла регистров (и там и там косвенная адресация из-за ассоциативности, для кеша данных — с данными, для файла регистров — из-за переименований регистров, где внутренний файл регистров в несколько раз больше "публичного" его отображения).


V>>Включая здравый смысл, Intel не выгодно скрывать от общественности ту информацию, которая поможет скомпиллировать под их процессоры наилучший код.

S>Я имею в виду, что вряд ли бы они стали перемешивать код просто так, не имея к этому соображений.

Ес-но, я лишь возражал, что для этого применялись недоступные простым смертным знания.
Мне попадались в разные времена док-ты от Intel по способам увеличения эффективности исполняемого на их процах кода.
Другое дело, что там много инфы и она достаточно "муторная". ))


V>>"Я в курсе" не согласуется с написанным тобой выше:

V>>

V>>>>Формулы одинаковые для каждого канала, просто в памяти эффективней расположить подряд данные от одного канала, а не "смесь" их.
S>>>По-прежнему не вижу обоснования этой эффективности.
S>>>Например, если мне нужно умножить оба канала на 0.74, то я просто

S>Вполне согласуется. Если формула одинаковая, то она будет записываться в виде SIMD хоть для отдельных каналов, хоть для пары каналов через один.

Но для второго канала для рекурсивного фильтра надо подгрузить уже другие минус N отсчётов.


S>Та же свёртка просто будет не с вектором (a0, a1, a2, a3), а c вектором (a0, a0, a1, a1).

S>Кстати, каков типичный размер IR для практических фильтров?

В теории IR бесконечен, на практике берут некую заданную точность.
https://studfile.net/preview/5830088/page:5/

Соотв, кол-во отсчётов IR зависит от отношения частоты дискретизации / частоты фильтра и от заданной точности.
В реальности это от нескольких десятков отсчётов, до нескольких тысяч, чаще всего в пределах нескольких сотен для низких порядков фильтра (до ~8 порядков).

Для ревербераторов+эха длина IR может составлять секунды, умножай на частоту дискретизации.

Для IR микрофонно-аккустических систем (т.н. кабинеты) — десятые доли секунд.


V>>Например, подобного рода баловство:

V>>http://files.rsdn.org/21096/MusicFX.png
S>Ну, так это ж картинка. Надо смотреть внутрь — как там эти фильтры представлены.

Это курсовик одного из сыновей по обработке сигналов, я ему в основном с графикой и помог на начальном этапе (для быстрого старта, показал принцип как быстро накидать "солидно" выглядящее GUI в OpenGL, в т.ч. подготовить и оживить все эти картинки девайсов, где на каждый девайс ушло где-то 15-20 мин).
Исходники есть, можно подключить гитару и играть. ))
Могу переслать куда-нить.


S>И можно ли представить их лучше/удобнее, чем они уже представлены.


Можно.
Вернее, нужно, чтобы стимулировать желание экспериментировать задёшево. ))


V>>Если ты вплотную к плюсам подобрался, то представления о том, как должны выглядеть современные эксперименты, скажем, в области ИИ, могут малость поменяться. ))

S>Мои представления изменить нетрудно — я об этом знаю примерно ноль.

ОК, это была инфа — эксперименты/рассчёты ИИ делают сегодня в основном на Питоне.
Т.е., в реальных железках работают уже плюсы с вычисленными на Питоне коэф. сетки.
Отредактировано 27.10.2020 17:40 vdimas . Предыдущая версия . Еще …
Отредактировано 27.10.2020 17:37 vdimas . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.