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 объекта)
Можете обновить часть "На текущий момент async2 остаётся экспериментом, доступным в runtimelab в ветке feature/async2-experiment" -- оно уже переехало в апстрим (main dotnet/runtime) и продолжается разработка уже там (довольно активно).
Я бы предложил такой:
Здесь нет ничего небезопасного, он не взорвется с 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 - вот переезд + много пулл-реквестов после этого
он переехал. вы ожидали увидеть удаленную ветку? зачем?
Можете обновить часть "На текущий момент async2 остаётся экспериментом, доступным в runtimelab в ветке feature/async2-experiment" -- оно уже переехало в апстрим (main dotnet/runtime) и продолжается разработка уже там (довольно активно).