Информация об изменениях

Сообщение Re[31]: 2D-Linq и оптимизация цифровых фильтров - 3 от 12.07.2018 18:24

Изменено 12.07.2018 19:01 vdimas

Re[31]: 2D-Linq и оптимизация цифровых фильтров - 3
Здравствуйте, Sinclair, Вы писали:

V>>Для одномерного массива вот в этом случае: for(int k=0; k<array.Length; k++) sum+=array[k]; компилятор просто подставляет опкоды доступа к массиву безо всяких проверок.

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

В указанном мною цикле после дизассемблера никаких проверок доступа к массиву нет, есть только проверка переменной цикла, именно об этом я и говорил.


S>Разнице в эффективности — не противоречит. Утверждению — противоречит.


Настаиваю.
Я более чем уверен, что ты в курсе про трюк оптимизации показанного мною цикла.
Об этом и шла речь, ес-но.

V>>А, главное, почему это можно делать для linq-хелпера и нельзя делать для "обычных циклов"?

S>Потому что для "обычных" циклов этот корявый код пиннинга и адресной арифметики придётся писать вручную, рискуя накосячить.

ОК, смотрим, где я показывал чтение и запись в unsafe-обертку:
http://www.rsdn.org/forum/dotnet/7187427.1
    fixed (int* intArray = indexer.array)
    {
        var array = new UnsafeIntArray2D(intArray, dx, dy);

На всяк случай напомню, что переменная intArray является константной, т.е. переменную под fixed нельзя изменять.
Это значение можно лишь:
— передать куда-то (поле для ошибок всё еще очень узко);
— записать по указателю или прочитать из него, что заведомо абслютно безопасно, бо такова суть выражения fixed.

Смотрим код далее:
array[x, y] = rnd.Next();
...
sum += array[x, y];

Доступ как к обычному массиву, бояться нечего.


S>А для linq код порождает тупая неумолимая железяка, которая не делает ошибок.


Этот код писать тому же самому программисту, а не железяке.
Причём, присать на порядок-другой больше, чем ты уже написал.
Вот это уже страшно, бо поле для ошибок — у-у-у-у.

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

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

Помимо блура я давал еще кучу популярных эффектов.
И всё еще настаиваю, что все эти подробности являются неотъемлимой частью алгоритма и им самое место в самом же алгоритме.


S>Нет, не надо. Я же показал код — там нет никаких проверок, и при этом всё безопасно.


Минимум одна проверка есть — юзверская.


V>>Cамая издевательская тут последняя строка z — внутри Span<> всё-равно происходят проверки выхода за диапазон, хотя на строке x мы вышли за диапазон массива без проблем.

V>>Тоже налицо улыбающие попытки совместить ужа с ежом.
S>Налицо непонимание идеи, стоящей за Span<T>.

Ой, блин.
Сходил бы на гитхаб CoreFX, там были эпичное обсуждения насчёт этого Span, Memory, Sequence и т.д.


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


Да.


S>2. Обеспечить высокую эффективность.


Да.
Но за счёт игнора ошибок обращений к памяти.
Я же именно это только что продемонстрировал.


S>В частности, вот в таком цикле проверки на выход за границы делаются 0 раз:

S>
for(int i=0; i<a.Length; i++) Console.WriteLine(a[i]);


Я же говорил, что ты должен был быть вкурсе?
Ну и вот.


S>А в таком — 1 раз:

S> var s = new Span<int>(a, a.Length-1); // вот здесь делается единственная проверка

Во-первых, такой сигнатуры нет, есть чуть другая, с 2-мя проверками.
Во-вторых, еще раз, вот "это" живёт в той же сборке, что и Span<>:
MemoryMarshal.CreateReadOnlySpan(ref a[0], a.Length + 1);


Никакого тебе пина, никакого unsafe, т.е. в случае unsafe все хоть инстинктивно внимание включают, бо яички-то втягиваются... ))
А тут делай что хошь, гуляй по памяти.
Потому что проверка смешная.


S>Для перехода между вторым и третьим случаями нам и полезен Span<T>.


Я только что показал тебе, что совместили ужа с ежом.
Если бы для только для показанного тобой перехода, нельзя было бы породить Span от произвольной управляемой или неуправляемой ссылки с произвольной длинной последовательно. Но это делается как два пальца об асфальт.

В общем, вышло на троечку.


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

S>Мы — можем. Джит — нет. Он недостаточно умён, чтобы заметить, что мы учли размер ядра в диапазонах обхода.

А как ты учёл?
Через KernelMeasure? ))

Мне уже поднадоело напоминать, что это означает чёрти-что в прикладном плане.

Но фик с ним уже с прикладным планом, а так:
    int _x;
    public int x => (_x++)/100;
    ...
    from d in data select (d[-x, 0] + d[x, 0] + d[0, -x] + d[0, x])


А мужики-то linq и не в курсе...


S>Отказаться от проверок в дотнете можно только одним способом — адресной арифметикой.


Если бы только в дотнете. ))
О чём и речь.


S>Лепить адресную арифметику в каждом конкретном цикле — боже упаси.


Спрячь за хелперами.


S>А если мы захотим туда припилить SIMD интринсики и/или многопоточность, то там вообще будет туши свет.


Тоже спрячь за хелперами.

Вплоть до того, что можно через foreach организовать на хелперах, типа так:
foreach(row in array.Slice(+1, -1))    // пусть отрицательное число означает отсчёт с конца
  foreach(cell in row.Slice(+1, -1))
      cell.Put((cell[-1, 0]+cell[1, 0]+cell[0, -1]+cell[0, 1])/4);


Итого, две проверки в начале плюс две проверки на каждую строку.
А накаждую точку гнать уже без проверки.
ИМХО, вполне себе компроммис.


S>Такие вещи перемешивать с прикладным кодом строго противопоказано.


Да куда ты денешься? ))
Аппелировать своим мини-примером, мол, "вот же делся" тут бесполезно более чем.


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

S>Пока что никаких противоречий я не вижу.

А, ну да.
Если зрение специальным образом поднастроить.

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


S>Задача была не в том, чтобы сделать код MSIL-генератора идеальным, понятным, и расширяемым. Задача была вообще проверить, можно ли заинлайнить делегат принудительно;


Делегат?
Конечно можно.
А зачем это "проверять"?
Можно даже собственный метод сгенерить вместо его Invoke.
Не пробовал? — попробуй.

Одно но — если делегат хранит ссылку не на статический метод, а на метод некоего объекта, то при таком инлайне надо заводить переменную на стеке и копировать туда этот объект. Учёл?


S>можно ли извлечь информацию о размере ядра программно


Ну вот я показал как поломать.


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


Не зная подробностей алгоритма?
Одноначно нельзя, ес-но.


S>Пока что в качестве черновика годится.


И я про то же.


V>>Тем более, что в итоге и обобщённого решения не получилось, и частного тоже.

V>>Подай в свой алгоритм матрицу {{2, 2}, {2, 2}} и убедись, что с4 блур обсчитывается не правильно. ))
S>Он обсчитывается ровно настолько же правильно, как и для матрицы 3x3. А что будет с матрицей в вашем алгоритме?

Ну я там тебе перечислил россыпь их — ткни в любой, посчитаю. ))

"Мою" версию я уже обрисовывал — тело отдельно, краевые эффекты отдельно.
Вот здесь отделять мух от котлет можно и нужно.
Re[31]: 2D-Linq и оптимизация цифровых фильтров - 3
Здравствуйте, Sinclair, Вы писали:

V>>Для одномерного массива вот в этом случае: for(int k=0; k<array.Length; k++) sum+=array[k]; компилятор просто подставляет опкоды доступа к массиву безо всяких проверок.

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

В указанном мною цикле после дизассемблера никаких проверок доступа к массиву нет, есть только проверка переменной цикла, именно об этом я и говорил.


S>Разнице в эффективности — не противоречит. Утверждению — противоречит.


Настаиваю.
Я более чем уверен, что ты в курсе про трюк оптимизации показанного мною цикла.
Об этом и шла речь, ес-но.

V>>А, главное, почему это можно делать для linq-хелпера и нельзя делать для "обычных циклов"?

S>Потому что для "обычных" циклов этот корявый код пиннинга и адресной арифметики придётся писать вручную, рискуя накосячить.

ОК, смотрим, где я показывал чтение и запись в unsafe-обертку:
http://www.rsdn.org/forum/dotnet/7187427.1
    fixed (int* intArray = indexer.array)
    {
        var array = new UnsafeIntArray2D(intArray, dx, dy);

На всяк случай напомню, что переменная intArray является константной, т.е. переменную под fixed нельзя изменять.
Это значение можно лишь:
— передать куда-то (поле для ошибок всё еще очень узко);
— записать по указателю или прочитать из него, что заведомо абслютно безопасно, бо такова суть выражения fixed.

Смотрим код далее:
array[x, y] = rnd.Next();
...
sum += array[x, y];

Доступ как к обычному массиву, бояться нечего.


S>А для linq код порождает тупая неумолимая железяка, которая не делает ошибок.


Этот код писать тому же самому программисту, а не железяке.
Причём, присать на порядок-другой больше, чем ты уже написал.
Вот это уже страшно, бо поле для ошибок — у-у-у-у.

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

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

Помимо блура я давал еще кучу популярных эффектов.
И всё еще настаиваю, что все эти подробности являются неотъемлимой частью алгоритма и им самое место в самом же алгоритме.


S>Нет, не надо. Я же показал код — там нет никаких проверок, и при этом всё безопасно.


Минимум одна проверка есть — юзверская.


V>>Cамая издевательская тут последняя строка z — внутри Span<> всё-равно происходят проверки выхода за диапазон, хотя на строке x мы вышли за диапазон массива без проблем.

V>>Тоже налицо улыбающие попытки совместить ужа с ежом.
S>Налицо непонимание идеи, стоящей за Span<T>.

Ой, блин.
Сходил бы на гитхаб CoreFX, там были эпичное обсуждения насчёт этого Span, Memory, Sequence и т.д.


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


Да.


S>2. Обеспечить высокую эффективность.


Да.
Но за счёт игнора ошибок обращений к памяти.
Я же именно это только что продемонстрировал.


S>В частности, вот в таком цикле проверки на выход за границы делаются 0 раз:

S>
for(int i=0; i<a.Length; i++) Console.WriteLine(a[i]);


Я же говорил, что ты должен был быть вкурсе?
Ну и вот.


S>А в таком — 1 раз:

S> var s = new Span<int>(a, a.Length-1); // вот здесь делается единственная проверка

Во-первых, такой сигнатуры нет, есть чуть другая, с 2-мя проверками.
Во-вторых, еще раз, вот "это" живёт в той же сборке, что и Span<>:
MemoryMarshal.CreateReadOnlySpan(ref a[0], a.Length + 1);


Никакого тебе пина, никакого unsafe, т.е. в случае unsafe все хоть инстинктивно внимание включают, бо яички-то втягиваются... ))
А тут делай что хошь, гуляй по памяти.
Потому что проверка смешная.


S>Для перехода между вторым и третьим случаями нам и полезен Span<T>.


Я только что показал тебе, что совместили ужа с ежом.
Если бы для только для показанного тобой перехода, нельзя было бы породить Span от произвольной управляемой или неуправляемой ссылки с произвольной длинной последовательности. Но это делается как два пальца об асфальт.

В общем, вышло на троечку.


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

S>Мы — можем. Джит — нет. Он недостаточно умён, чтобы заметить, что мы учли размер ядра в диапазонах обхода.

А как ты учёл?
Через KernelMeasure? ))

Мне уже поднадоело напоминать, что это означает чёрти-что в прикладном плане.

Но фик с ним уже с прикладным планом, а так:
    int _x;
    public int x => (_x++)/100;
    ...
    from d in data select (d[-x, 0] + d[x, 0] + d[0, -x] + d[0, x])


А мужики-то linq и не в курсе...


S>Отказаться от проверок в дотнете можно только одним способом — адресной арифметикой.


Если бы только в дотнете. ))
О чём и речь.


S>Лепить адресную арифметику в каждом конкретном цикле — боже упаси.


Спрячь за хелперами.


S>А если мы захотим туда припилить SIMD интринсики и/или многопоточность, то там вообще будет туши свет.


Тоже спрячь за хелперами.

Вплоть до того, что можно через foreach организовать на хелперах, типа так:
foreach(row in array.Slice(+1, -1))    // пусть отрицательное число означает отсчёт с конца
  foreach(cell in row.Slice(+1, -1))
      cell.Put((cell[-1, 0]+cell[1, 0]+cell[0, -1]+cell[0, 1])/4);


Итого, две проверки в начале плюс две проверки на каждую строку.
А накаждую точку гнать уже без проверки.
ИМХО, вполне себе компроммис.


S>Такие вещи перемешивать с прикладным кодом строго противопоказано.


Да куда ты денешься? ))
Аппелировать своим мини-примером, мол, "вот же делся" тут бесполезно более чем.


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

S>Пока что никаких противоречий я не вижу.

А, ну да.
Если зрение специальным образом поднастроить.

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


S>Задача была не в том, чтобы сделать код MSIL-генератора идеальным, понятным, и расширяемым. Задача была вообще проверить, можно ли заинлайнить делегат принудительно;


Делегат?
Конечно можно.
А зачем это "проверять"?
Можно даже собственный метод сгенерить вместо его Invoke.
Не пробовал? — попробуй.

Одно но — если делегат хранит ссылку не на статический метод, а на метод некоего объекта, то при таком инлайне надо заводить переменную на стеке и копировать туда этот объект. Учёл?


S>можно ли извлечь информацию о размере ядра программно


Ну вот я показал как поломать.


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


Не зная подробностей алгоритма?
Одноначно нельзя, ес-но.


S>Пока что в качестве черновика годится.


И я про то же.


V>>Тем более, что в итоге и обобщённого решения не получилось, и частного тоже.

V>>Подай в свой алгоритм матрицу {{2, 2}, {2, 2}} и убедись, что с4 блур обсчитывается не правильно. ))
S>Он обсчитывается ровно настолько же правильно, как и для матрицы 3x3. А что будет с матрицей в вашем алгоритме?

Ну я там тебе перечислил россыпь их — ткни в любой, посчитаю. ))

"Мою" версию я уже обрисовывал — тело отдельно, краевые эффекты отдельно.
Вот здесь отделять мух от котлет можно и нужно.