а когда метод становится горячим (30+ вызовов), перекомпилирует с агрессивными оптимизациями
На самом деле эвристика гораздо сложнее, там рантайму нужно еще найти окно когда процессор не занят другими компиляциями уже 100мс и т.п.
Microsoft прогнала стандартную библиотеку через тысячи тестов
Там на самом деле не такой большой статик-профиль собирается, пару приложений всего :-)
В обычном R2R каждая DLL компилируется отдельно, и если метод из Assembly1.dll вызывает маленький метод из Assembly2.dll, inlining невозможен
И без композитного режима, у crossgen есть флаг, который разрешает такой инлайниг, там что-то типа -opt-cross-module=
R2R не нужен или вреден для long-running сервисов
Не очень понял почему вреден. Всё горячее, по вашим же словам, все равно откомпилируется в Tier1.
DOTNET_JitDisasm=MyMethod dotnet run
не советую так делать, потому что dotnet run вызовет мсбилд/нугет/розлин и вы можете получить дизасм оттуда если там тоже есть метод с таким именем (если не полное имя указываете)
R2R и Native AOT это разные вещи,Native AOT в .NET 7+ вообще убирает JIT и производит чисто нативный бинарник, R2R это гибрид native-кода и JIT.
+/- одно и то же, даже кодобаза та же. Оба использует тот же джит для АОТ кода, разница в том что у наот есть точный депенденси анализ, а р2р он не нужен и ему не надо ничего триммить
Это не правда. Вы имели ввиду авто-векторизацию циклов. И это не бесполезно, просто в дотнете на любой чих есть апишка под любой вид операции над массивом, и она уже обмазана симдами. Общей векторизации (ее еще называют SLP vectorization) в джите дофига, просто циклы не разворачивает
По сему автовекторизации нет и она не планируется к добавлению
Одна из причин, почему векторизация циклов очень сложна к реализации - это то, что модель памяти запрещает мержить обращения к массиву в симд если нарушится атомарность, из-за чего единственный вариант - пинить массив (чтобы гц не мог изменить выравнивание), выравнивать по границе размера вектора и потом оптимизировать. Никто не сказал что ее точно не будет, не воспринимайте болтавню в дискорде как последнюю инстанцию
У вас есть процессор на архитектуре x86, под которую собираете R2R, какие "конкретные" инструкции в данном случае и для приведённого в примере кода вы имеете в виду? Всякие popcnt и им подобные расширения x86, которые появились в процессорах не сразу, здесь не применимы.
R2R не обязательно компилировать под генерик процессор, в крайнем случае, на старом процессоре рантайм просто инвалидирует те Р2Р коды, которые требуют ISA которых нет. Более того, в .NET 11 сильно подняли baseline в R2R, там вроде и popcnt и AVX есть теперь (а значит и VEX).
private static string StackallocBuffer(string s)
{
var buffer = s.Length <= 64 ? stackalloc char[64] : new char[s.Length];
var index = 0;
foreach (var c in s)
{
if (!char.IsWhiteSpace(c))
buffer[index++] = c;
}
return new string(buffer[..index]);
}
Здесь нет ничего небезопасного, он не взорвется с StackOverflow на большой строке и const-sized стекаллок проинициализируется нулями примерно 2 инструкциями (через avx512 zmm) + как показывает практика, большинств строк - маленькие, да и не надо боятся эфемерных GC аллокаций, они не создают проблем для гц.
Признаю я не дочитал до конца и мне показалось что вы советовали это как оптимизацию, извиняюсь. Но тем не менее SkipLocalsInit штука которую лучше никак не рекламировать, потому что это хороший такой способ прострелить себе ноги. безопасный C# всегда инициализирует абсолютно все нулями и лучше это так и оставить.
Указатели не единственное что является unsafe кодом в .NET. На данный момент это так, в .NET 11/12 это изменится и много текущих API cтанут unsafe + Span который инициализируется мусором со стека (т.е. с SkipLocalsInit) станет требовать unsafe блок, вы можете прочитать этот тут: https://github.com/dotnet/csharplang/blob/main/proposals/unsafe-evolution.md#stack-allocation
The stackalloc_expression is used within a member that has SkipLocalsInitAttribute applied.
Но мне кажется это и так очевидно что переменные инициализируемые мусором со стека воняют unsafe кодом.
Полагаю требуется некий контекст, вполне есть ситуации где можно гарантировать отсутствие как освобождения в try, так и использования после finally, а исключения остаются проблемой.
Я имел ввиду как общий совет это неверно, и уже приводило к CVE, поэтому в коде dotnet/runtime такой паттерн избегают (хотя там есть и try-finally).
Ещё finally намертво предотвращает инлайнинг,
Начиная с .NET 9.0 (или 10.0, надо проверить) это уже не так
Лучше делать это в блоке finally, чтобы гарантировать возврат даже если в процессе обработки вылетит исключение.
Это неверный совет. Лучше никогда не возращать массив в пул чем вернуть его в finally с риском use-after-free/double-free. Ничего страшного не будет, если вы не вернете массив назад в пул, это не приведет к мемори ликам. Это даже прописано в гайдлайнах к unsafe code. (Да, ArrayPool это по факту - unsafe code).
Мой поинт в том что вы рекламируете это как библиотеку и у вас ни слова нет про то насколько это хрупкий/ненадежный около-UB/unsafe инструмент. Что бы вы понимали, бинарные сериализаторы, которые делают похожие хаки сериализуя объекты в байт массивы (и десереиализуя) без учета паддингов были не раз причиной CVE.
Вашу задачу невозможно решить в общем случае без около-UB люто unsafe кода (который обязательно выстрелит в виде CVE) в .NET. Как я вам уже написал: Единственное когда это возможно это для структур без падингов и блиттабл полями, но в дотнете нет АПИ через которое вы можете это проверить
Если вы не используете unsafe код, вы никогда не столкнетесь с никакими багами из-за мусора в паддинга (он там может быть даже без интеропа, просто со стека)
Абсолютно бессмысленнго его занулять для структур без StructLayout.Explicit, потому что JIT может легко структуру с зануллеными падингами временно куда-то скопировать на стек только полями и будет опять мусор со стека в полях, короче говоря, это UB.
Никогда не пишите код который считает хэш, сравнивает структуры через memcpy/memcmp идиомы, это отличный способ выстрелить себе в ногу с гранатомета. Единственное когда это возможно это для структур без падингов и блиттабл полями, но в дотнете нет АПИ через которое вы можете это проверить (+ может быть различие на разных архитектурах/платформах)
похоже GC .net останавливает потоки в основном на аккокаторах
это не так. часть методов в дотнет полностью fully interruptible и в какой момент бы поток не приостановился - он будет в сейфпоинте. в других методах используется return address hijacking т.е. любой вызов любого метода становится потенциальным сейфпоинтом.
Плюсы: никаких лишних инструкций в циклах и где либо еще, мгновенная остановка треда в fully-interruptible Минусы: большой (по размеру) GC info (чтобы precise GC знал что сканировать на каждой инструкции fully-interruptible метода. Менее прогнозируемое время до паузы при call hijacking. необходимость сохранять и ресторить return address в стек в каждом методе
Нет. это не GC poll, вы смотрите дебажный асм (даже если запустили в Release, т.к. при отладке вижла всегда сбрасывает код в Tier0-dbg) и условный переход который вы мне показываете это
cmp dword ptr [(reloc 0x7ffe048bca18)], 0
je SHORT G_M000_IG04
G_M000_IG03: ;; offset=0x0021
call CORINFO_HELP_DBG_IS_JUST_MY_CODE
Это подсказка деббагеру что код вошёл в "my code" для фичи "Debug just my code"
Ещё раз повторю - .NET не использует явные сейф поинты (как, например, Java) - у этого подхода свои плюсы/минусы.
Если вы использовали .NET чтобы рассказать как работает GC то ваша интерпретация очень далека от того как он работает - там нет GC_POLL вызовов в коде (за очень редким исключением аля pinvokes) циклах и т.п.
Обычное интерпретирование заданных правил из строки (поведение по-умолчанию)
Динамического создания нового IL метода который оптимизирует какие-то операции конкретно под заданную регулярку.
Тоже самое что 2) только через Source-Gen на этапе компиляции C# кода, а не как в случае 2) - во время работы
В целом рекомендуется использовать 3ий вариант, он самый быстрый и вы не делаете лишней работы во время запуска приложения (= ускорение запуска) + дебажить просто. Есть всякие автоматические инспекции которые помогут привести обычную регулярку к нему
Как видите, никакое сохранение ни в какую переменную само по себе ничего не дает (кроме разве что сохранения аллокации 1 объекта)
На самом деле эвристика гораздо сложнее, там рантайму нужно еще найти окно когда процессор не занят другими компиляциями уже 100мс и т.п.
Там на самом деле не такой большой статик-профиль собирается, пару приложений всего :-)
И без композитного режима, у crossgen есть флаг, который разрешает такой инлайниг, там что-то типа -opt-cross-module=
Не очень понял почему вреден. Всё горячее, по вашим же словам, все равно откомпилируется в Tier1.
не советую так делать, потому что dotnet run вызовет мсбилд/нугет/розлин и вы можете получить дизасм оттуда если там тоже есть метод с таким именем (если не полное имя указываете)
+/- одно и то же, даже кодобаза та же. Оба использует тот же джит для АОТ кода, разница в том что у наот есть точный депенденси анализ, а р2р он не нужен и ему не надо ничего триммить
Это не правда. Вы имели ввиду авто-векторизацию циклов. И это не бесполезно, просто в дотнете на любой чих есть апишка под любой вид операции над массивом, и она уже обмазана симдами. Общей векторизации (ее еще называют SLP vectorization) в джите дофига, просто циклы не разворачивает
Одна из причин, почему векторизация циклов очень сложна к реализации - это то, что модель памяти запрещает мержить обращения к массиву в симд если нарушится атомарность, из-за чего единственный вариант - пинить массив (чтобы гц не мог изменить выравнивание), выравнивать по границе размера вектора и потом оптимизировать. Никто не сказал что ее точно не будет, не воспринимайте болтавню в дискорде как последнюю инстанцию
R2R не обязательно компилировать под генерик процессор, в крайнем случае, на старом процессоре рантайм просто инвалидирует те Р2Р коды, которые требуют ISA которых нет. Более того, в .NET 11 сильно подняли baseline в R2R, там вроде и popcnt и AVX есть теперь (а значит и VEX).
Я бы предложил такой:
Здесь нет ничего небезопасного, он не взорвется с StackOverflow на большой строке и const-sized стекаллок проинициализируется нулями примерно 2 инструкциями (через avx512 zmm) + как показывает практика, большинств строк - маленькие, да и не надо боятся эфемерных GC аллокаций, они не создают проблем для гц.
Признаю я не дочитал до конца и мне показалось что вы советовали это как оптимизацию, извиняюсь. Но тем не менее SkipLocalsInit штука которую лучше никак не рекламировать, потому что это хороший такой способ прострелить себе ноги. безопасный C# всегда инициализирует абсолютно все нулями и лучше это так и оставить.
Указатели не единственное что является unsafe кодом в .NET. На данный момент это так, в .NET 11/12 это изменится и много текущих API cтанут unsafe + Span который инициализируется мусором со стека (т.е. с SkipLocalsInit) станет требовать unsafe блок, вы можете прочитать этот тут: https://github.com/dotnet/csharplang/blob/main/proposals/unsafe-evolution.md#stack-allocation
Но мне кажется это и так очевидно что переменные инициализируемые мусором со стека воняют unsafe кодом.
SkipLocalsInit требует AllowUnsafeBlock, ваш код всё ещё unsafe.
вы же видите что у вас Error примерно равен разнице результатов?
Я имел ввиду как общий совет это неверно, и уже приводило к CVE, поэтому в коде dotnet/runtime такой паттерн избегают (хотя там есть и try-finally).
Начиная с .NET 9.0 (или 10.0, надо проверить) это уже не так
Это неверный совет. Лучше никогда не возращать массив в пул чем вернуть его в finally с риском use-after-free/double-free. Ничего страшного не будет, если вы не вернете массив назад в пул, это не приведет к мемори ликам. Это даже прописано в гайдлайнах к unsafe code. (Да, ArrayPool это по факту - unsafe code).
Забавно что вы написали небезопасный хэшер который считает с мусором в хеше, и потом героически решили это проблему через другую библиотеку, которая в комплект не входит :D кстати у вас там явно баг в коде https://github.com/viruseg/GxHash.128bits.Overloads/blob/master/GxHash.128bits.Overloads/Hash128Methods.cs#L190 - вместо buffer.Length должно было видимо бы asBytes.Length
Мой поинт в том что вы рекламируете это как библиотеку и у вас ни слова нет про то насколько это хрупкий/ненадежный около-UB/unsafe инструмент. Что бы вы понимали, бинарные сериализаторы, которые делают похожие хаки сериализуя объекты в байт массивы (и десереиализуя) без учета паддингов были не раз причиной CVE.
Вашу задачу невозможно решить в общем случае без около-UB люто unsafe кода (который обязательно выстрелит в виде CVE) в .NET. Как я вам уже написал: Единственное когда это возможно это для структур без падингов и блиттабл полями, но в дотнете нет АПИ через которое вы можете это проверить
Если вы не используете unsafe код, вы никогда не столкнетесь с никакими багами из-за мусора в паддинга (он там может быть даже без интеропа, просто со стека)
Абсолютно бессмысленнго его занулять для структур без StructLayout.Explicit, потому что JIT может легко структуру с зануллеными падингами временно куда-то скопировать на стек только полями и будет опять мусор со стека в полях, короче говоря, это UB.
Никогда не пишите код который считает хэш, сравнивает структуры через memcpy/memcmp идиомы, это отличный способ выстрелить себе в ногу с гранатомета. Единственное когда это возможно это для структур без падингов и блиттабл полями, но в дотнете нет АПИ через которое вы можете это проверить (+ может быть различие на разных архитектурах/платформах)
Рекомендую к прочтению статью.
это не так. часть методов в дотнет полностью fully interruptible и в какой момент бы поток не приостановился - он будет в сейфпоинте. в других методах используется return address hijacking т.е. любой вызов любого метода становится потенциальным сейфпоинтом.
Плюсы: никаких лишних инструкций в циклах и где либо еще, мгновенная остановка треда в fully-interruptible
Минусы: большой (по размеру) GC info (чтобы precise GC знал что сканировать на каждой инструкции fully-interruptible метода. Менее прогнозируемое время до паузы при call hijacking. необходимость сохранять и ресторить return address в стек в каждом методе
Нет. это не GC poll, вы смотрите дебажный асм (даже если запустили в Release, т.к. при отладке вижла всегда сбрасывает код в Tier0-dbg) и условный переход который вы мне показываете это
Это подсказка деббагеру что код вошёл в "my code" для фичи "Debug just my code"
Ещё раз повторю - .NET не использует явные сейф поинты (как, например, Java) - у этого подхода свои плюсы/минусы.
нет никаких ассемблерных вставок для GC_POLL в .NET. Покажите мне где вот тут (sharplab) вставка?
Если вы использовали .NET чтобы рассказать как работает GC то ваша интерпретация очень далека от того как он работает - там нет GC_POLL вызовов в коде (за очень редким исключением аля pinvokes) циклах и т.п.
У регулярок есть 3 режима:
Обычное интерпретирование заданных правил из строки (поведение по-умолчанию)
Динамического создания нового IL метода который оптимизирует какие-то операции конкретно под заданную регулярку.
Тоже самое что 2) только через Source-Gen на этапе компиляции C# кода, а не как в случае 2) - во время работы
В целом рекомендуется использовать 3ий вариант, он самый быстрый и вы не делаете лишней работы во время запуска приложения (= ускорение запуска) + дебажить просто. Есть всякие автоматические инспекции которые помогут привести обычную регулярку к нему
Как видите, никакое сохранение ни в какую переменную само по себе ничего не дает (кроме разве что сохранения аллокации 1 объекта)
Ваша реализация всё ещё проигрывает озвученному выше
return value.AsSpan().IndexOfAnyExceptInRange('0', '9') == -1;https://github.com/dotnet/runtime/pull/114675 и https://github.com/dotnet/runtime/pull/114861 - вот переезд + много пулл-реквестов после этого