InlineArray даст защиту от выхода за границы (через исключение). в вашем случае это полный ансейф, сугубо на ваших плечах отвественность что вы правильно посчитали длины и оффсеты у Array
std::array<int, 10> -- это и есть const generics, под них надо изменять метадату, может когда и сделают, но для этого нужна везкая причина (в идеале, еще фичи которым нужны изменения)
var handlePtr = (IntPtr)Unsafe.AsPointer(ref handle);
Сразу видно, не читали статью ;-) вы тут делаете предположение что handle всегда на стеке или в нативной памяти, хотя ничего не помешает ему быть, например, полем класса или статическим полем и привет GC corruption баг (если это не ref struct конечно).
Т.е. вы используете неявный контракт вызова вашего метода, который никому не известен
POH не очень удачная фича по итогу получилась, лучше бы ее вообще не делали. Публичные апи к ней привели к тому что смесь долго- и коротко- живущий пиннед массивов приводят в дикой фрагментизации кучи которую нельзя дефрагментировать.
Я лично не сильно фанат заметания unsafe под ковер. если у вас интероп слой, то и используйте в нем uv_getaddrinfo_t* и маршаллить ничего не надо, сразу в сигнатуре параметра объявить
К сожалению, довольно много (в разы больше чем в скажем в мире JVM). Интеропа очень много и все те проавила описанные тут для него так же верны. Но очень часто скучающие программисты начинают что-то микрооптимимзировать через unsafe
А так еще unsafe для fixed size buffer иногда полезен
Хотелось бы, чтобы fixed-size buffers полностью ушли в прошлое и заменились inline arrays.
Как stackalloc стал доступен из safe
По текущему плану C# команды, даже если вы используете Span<> x = stackalloc но при этом у вас где-то (над методом или во всей сборке) указан skiplocalsinit, то всё выражение всё равно начнет требовать unsafe context.
а когда метод становится горячим (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).
stackalloc или нет, это всё еще никак не связано с кэшем процессора или оперативной памятью, ортогональные вещи.
InlineArray даст защиту от выхода за границы (через исключение). в вашем случае это полный ансейф, сугубо на ваших плечах отвественность что вы правильно посчитали длины и оффсеты у Array
std::array<int, 10> -- это и есть const generics, под них надо изменять метадату, может когда и сделают, но для этого нужна везкая причина (в идеале, еще фичи которым нужны изменения)
Сразу видно, не читали статью ;-) вы тут делаете предположение что handle всегда на стеке или в нативной памяти, хотя ничего не помешает ему быть, например, полем класса или статическим полем и привет GC corruption баг (если это не ref struct конечно).
Т.е. вы используете неявный контракт вызова вашего метода, который никому не известен
Что конкретно непонятно? В целом, можно было реализовать через const generics, но неизвестно будут ли они когда-либо из-за сложности
POH не очень удачная фича по итогу получилась, лучше бы ее вообще не делали. Публичные апи к ней привели к тому что смесь долго- и коротко- живущий пиннед массивов приводят в дикой фрагментизации кучи которую нельзя дефрагментировать.
они являются unsafe потому что ничего вам не мешает ошибится с размером и это не вызовет никаких рантайм проверок. Вот пример:
такой код даже отработает (и станет кандидатом в CVE)
Я лично не сильно фанат заметания unsafe под ковер. если у вас интероп слой, то и используйте в нем
uv_getaddrinfo_t*и маршаллить ничего не надо, сразу в сигнатуре параметра объявитьК сожалению, довольно много (в разы больше чем в скажем в мире JVM). Интеропа очень много и все те проавила описанные тут для него так же верны. Но очень часто скучающие программисты начинают что-то микрооптимимзировать через unsafe
Хотелось бы, чтобы fixed-size buffers полностью ушли в прошлое и заменились inline arrays.
По текущему плану C# команды, даже если вы используете
Span<> x = stackallocно при этом у вас где-то (над методом или во всей сборке) указан skiplocalsinit, то всё выражение всё равно начнет требовать unsafe context.В С/С++ ровно такой же каст вообще-то считается UB ;-)
На самом деле эвристика гораздо сложнее, там рантайму нужно еще найти окно когда процессор не занят другими компиляциями уже 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).