Сообщение Re[27]: Технология .Net уходит в небытиё от 25.01.2019 21:04
Изменено 25.01.2019 22:08 alexzzzz
A>>- мой вариант C#: 4,6с
_>Похоже на правду, хотя и чуть подправленный у тебя там алгоритм (оригинальный будет всё же порядка 5 секунд). Ну и надо отмечать, что это unsafe код, а safe вариант работает больше 10 секунд.
Что ты к unsafe привязался? Safe-вариант C++ работает больше 20 секунд, если таковым считать отладочный билд с разными проверками.
A>>- похожий C# в Unity: 2,6с
_>А вот тут у тебя уже совсем другой алгоритм (более простой для SIMD оптимизации). И если запустить его аналог на C++ (см. предыдущее сообщение), то будут совсем другие результаты (и на моей машине и на твоей).
Алгоритм там везде один и тот же: перебрать все ячейки и каждую неравную FFFFFF заменить на среднее значение восьми её соседей. Идея сравнивать, как себя ведёт одинаковый до буквы код в разных языках, мне непонятна. Есть входные данные и есть представление, как должны выглядеть выходные. Берёшь в руки конкретный язык с его инструментами и с их помощью трансформируешь.
Вот · на Яве подошёл к проблеме кардинальнее, принципиально изменив порядок действий. И честно говоря, правильно сделал. Задача любой программы ― трансформировать одни данные в другие данные. Если программа работает правильно и быстро, каким образом она этого достигает, никого парить не должно. Можно лишь завидовать.
По оптимизациям, там в результате какая-то длиннющая лапша из SSE и AVX. Ни малейшего представления, что вообще происходит. Но если исходный код цикла немного помассировать, можно получить ещё два варианта: простейший тупой на «обычных» регистрах (буквально десяток операций) и ещё один, по сути как предыдущий, но на SSE-регистрах. Эти два варианта отличались от лучшего на несколько десятых секунды и признаков векторизации в них не замечено. (Если конечно мне показывали правильный дизасм).
_>Для gcc и clang любых версий и под любыми ОС правильными опциями для подобных тестов будут: -std=c++17 -march=native -O3.
Core i5-2500K @4,5ГГц / DDR3 1333 МГц
-gcc: 6,0c | |
| |
-clang: 6,0c | |
| |
— msvc: 5,2с (компилировал в Студии, Favor Speed (/Ox), какие были остальные ключи — не поручусь)
— icc: 4,6с
Интеловский сегодня скачал на посмотреть. Он сам интегрировался в Студию, код компилировался там же с ключом -O3; между -O1 и -O3 разница была явно заметна в пользу -O3. За остальные не поручусь. Разных тонких настроек там до чёрта, и хз надо их трогать или нет.
_>Ну пока что из принципиальных различий я вижу замену if'а на некий math.select — это было обязательно делать? Неужели такая базовая операция как if запрещена? Или это ты сделал не вынужденно, а для нечестной оптимизации (см. ниже)?
Чего не честный-то? Я не понимаю логику. Если у тебя в С++ ?: работает быстрее if, зачем искусственно замедлять собственный код? Не всё ли равно, с какой скоростью в каком-то условном Коболе будет работать оператор ?: и есть ли он там вообще. Мне лично плевать. Надо сделать быстрее ― берёшь и делаешь быстрее, без оглядки на посторонних.
Конкретно math.select — интринзик, если может, мапится в blendvps или типа того, если не может, то
/// <summary>Returns b if c is true, a otherwise.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int select(int a, int b, bool c) { return c ? b : a; }
_>Ну и главное по этому твоему коду (который HPC#): это другой алгоритм (более толерантный к SIMD) и если ты хочешь использовать его именно в таком виде (тот if в моём тесте там совсем не просто так был, а как одна из сильно напрягающих SIMD оптимзиацию конструкций), то тогда уж надо и у других языков поменять.
Я обеими руками за то, чтобы на всех языках писать так, как наиболее эффективно писать в этих языках. Если вариант на C++ можно сделать в несколько раз быстрее, его надо сделать в несколько раз быстрее. Будет сравнение не сферических коней, а настоящего кода, и по нему можно будет ориентироваться.
А, не, это я дебил. Воткнул ?: внутрь if. Перепроверил:
— gcc: 2,7 вместо 6,0
— clang: 6,1 вместо 6,0
— msvc: 5,4 вместо 5,2
— icc: 4,6 ― без изменений
На всякий случай код | |
| |
_>Кроме этого, я вижу что работа идёт с некими NativeArray, а не стандартными .net массивами. Хотя это естественно не минус, но код всё же явно требует ручного портирования, а не просто некой перекомпиляции.
NativeArray ― это struct-обёртка над памятью, типа Span<T>/Memory<T>, но со встроенными защитными механизмами раннего отлова потенциальных race conditions. Например, если в одном потоке такой массив помечен атрибутом только для чтения, а в другом разрешён для записи, и если потоки не были правильно формально синхронизированы, то NativeArray гарантирует, что код мгновенно упадёт с объяснением, в каком месте проблема, в чём заключается и как исправить. Даже если потоки разошлись по времени и в данный момент проблемы как бы нет; достаточно лишь теоретической вероятности гонки. Или, допустим, потоки могут читать/писать в один и тот же массив, но каждый только в свою область; если поток по ошибке заденет чужую, то сразу стоп машина и красный флаг. Реально полезная штука.
В принципе, числодробилка может принимать на вход обычные управляемые массивы; если хочет, делать себе поверх них NativeArray<T>, безопасно работать с ними во множестве потоков, а результаты будут отображаться в исходные массивы. Пока не пробовал, но по идее можно работать и со Span<T>/Memory<T>, так даже универсальнее, чем только с массивами, но менее безопасно, чем с NativeArray<T>.
A>>- мой вариант C#: 4,6с
_>Похоже на правду, хотя и чуть подправленный у тебя там алгоритм (оригинальный будет всё же порядка 5 секунд). Ну и надо отмечать, что это unsafe код, а safe вариант работает больше 10 секунд.
Что ты к unsafe привязался? Safe-вариант C++ работает больше 20 секунд, если таковым считать отладочный билд с разными проверками.
A>>- похожий C# в Unity: 2,6с
_>А вот тут у тебя уже совсем другой алгоритм (более простой для SIMD оптимизации). И если запустить его аналог на C++ (см. предыдущее сообщение), то будут совсем другие результаты (и на моей машине и на твоей).
Алгоритм там везде один и тот же: перебрать все ячейки и каждую неравную FFFFFF заменить на среднее значение восьми её соседей. Идея сравнивать, как себя ведёт одинаковый до буквы код в разных языках, мне непонятна. Есть входные данные и есть представление, как должны выглядеть выходные. Берёшь в руки конкретный язык с его инструментами и с их помощью трансформируешь.
Вот · на Яве подошёл к проблеме кардинальнее, принципиально изменив порядок действий. И честно говоря, правильно сделал. Задача любой программы ― трансформировать одни данные в другие данные. Если программа работает правильно и быстро, каким образом она этого достигает, никого парить не должно. Можно лишь завидовать.
По оптимизациям, там в результате какая-то длиннющая лапша из SSE и AVX. Ни малейшего представления, что вообще происходит. Но если исходный код цикла немного помассировать, можно получить ещё два варианта: простейший тупой на «обычных» регистрах (буквально десяток операций) и ещё один, по сути как предыдущий, но на SSE-регистрах. Эти два варианта отличались от лучшего на несколько десятых секунды и признаков векторизации в них не замечено. (Если конечно мне показывали правильный дизасм).
_>Для gcc и clang любых версий и под любыми ОС правильными опциями для подобных тестов будут: -std=c++17 -march=native -O3.
Core i5-2500K @4,5ГГц / DDR3 1333 МГц
-gcc: 6,0c | |
| |
-clang: 6,0c | |
| |
— msvc: 5,2с (компилировал в Студии, Favor Speed (/Ox), какие были остальные ключи — не поручусь)
— icc: 4,6с
Интеловский сегодня скачал на посмотреть. Он сам интегрировался в Студию, код компилировался там же с ключом -O3; между -O1 и -O3 разница была явно заметна в пользу -O3. За остальные не поручусь. Разных тонких настроек там до чёрта, и хз надо их трогать или нет.
_>Ну пока что из принципиальных различий я вижу замену if'а на некий math.select — это было обязательно делать? Неужели такая базовая операция как if запрещена? Или это ты сделал не вынужденно, а для нечестной оптимизации (см. ниже)?
Чего не честный-то? Я не понимаю логику. Если у тебя в С++ ?: работает быстрее if, зачем искусственно замедлять собственный код? Не всё ли равно, с какой скоростью в каком-то условном Коболе будет работать оператор ?: и есть ли он там вообще. Мне лично плевать. Надо сделать быстрее ― берёшь и делаешь быстрее, без оглядки на посторонних.
Конкретно math.select — интринзик, если может, мапится в blendvps или типа того, если не может, то
/// <summary>Returns b if c is true, a otherwise.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int select(int a, int b, bool c) { return c ? b : a; }
_>Ну и главное по этому твоему коду (который HPC#): это другой алгоритм (более толерантный к SIMD) и если ты хочешь использовать его именно в таком виде (тот if в моём тесте там совсем не просто так был, а как одна из сильно напрягающих SIMD оптимзиацию конструкций), то тогда уж надо и у других языков поменять.
Я обеими руками за то, чтобы на всех языках писать так, как наиболее эффективно писать в этих языках. Если вариант на C++ можно сделать в несколько раз быстрее, его надо сделать в несколько раз быстрее. Будет сравнение не сферических коней, а настоящего кода, и по нему можно будет ориентироваться.
А, не, это я дебил. Воткнул ?: внутрь if. Перепроверил:
— gcc: 2,6 вместо 6,0
— clang: 6,1 вместо 6,0
— msvc: 5,4 вместо 5,2
— icc: 4,6 ― без изменений
На всякий случай код | |
| |
_>Кроме этого, я вижу что работа идёт с некими NativeArray, а не стандартными .net массивами. Хотя это естественно не минус, но код всё же явно требует ручного портирования, а не просто некой перекомпиляции.
NativeArray ― это struct-обёртка над памятью, типа Span<T>/Memory<T>, но со встроенными защитными механизмами раннего отлова потенциальных race conditions. Например, если в одном потоке такой массив помечен атрибутом только для чтения, а в другом разрешён для записи, и если потоки не были правильно формально синхронизированы, то NativeArray гарантирует, что код мгновенно упадёт с объяснением, в каком месте проблема, в чём заключается и как исправить. Даже если потоки разошлись по времени и в данный момент проблемы как бы нет; достаточно лишь теоретической вероятности гонки. Или, допустим, потоки могут читать/писать в один и тот же массив, но каждый только в свою область; если поток по ошибке заденет чужую, то сразу стоп машина и красный флаг. Реально полезная штука.
В принципе, числодробилка может принимать на вход обычные управляемые массивы; если хочет, делать себе поверх них NativeArray<T>, безопасно работать с ними во множестве потоков, а результаты будут отображаться в исходные массивы. Пока не пробовал, но по идее можно работать и со Span<T>/Memory<T>, так даже универсальнее, чем только с массивами, но менее безопасно, чем с NativeArray<T>.