Обновить
68
9.3
nagg@Nagg

Разработчик

Отправить сообщение

stackalloc или нет, это всё еще никак не связано с кэшем процессора или оперативной памятью, ортогональные вещи.

InlineArray даст защиту от выхода за границы (через исключение). в вашем случае это полный ансейф, сугубо на ваших плечах отвественность что вы правильно посчитали длины и оффсеты у Array

std::array<int, 10> -- это и есть const generics, под них надо изменять метадату, может когда и сделают, но для этого нужна везкая причина (в идеале, еще фичи которым нужны изменения)

var handlePtr = (IntPtr)Unsafe.AsPointer(ref handle);

Сразу видно, не читали статью ;-) вы тут делаете предположение что handle всегда на стеке или в нативной памяти, хотя ничего не помешает ему быть, например, полем класса или статическим полем и привет GC corruption баг (если это не ref struct конечно).

Т.е. вы используете неявный контракт вызова вашего метода, который никому не известен

Что конкретно непонятно? В целом, можно было реализовать через const generics, но неизвестно будут ли они когда-либо из-за сложности

POH не очень удачная фича по итогу получилась, лучше бы ее вообще не делали. Публичные апи к ней привели к тому что смесь долго- и коротко- живущий пиннед массивов приводят в дикой фрагментизации кучи которую нельзя дефрагментировать.

Даже не знаю, что эти методы делают в Unsafe т.к. границы блоков памяти известны.

они являются unsafe потому что ничего вам не мешает ошибится с размером и это не вызовет никаких рантайм проверок. Вот пример:

byte[] a = new byte[10];
byte[] b = new byte[10];
Unsafe.InitBlockUnaligned(ref a[0], value: 42, byteCount: 40);

такой код даже отработает (и станет кандидатом в CVE)

Я лично не сильно фанат заметания unsafe под ковер. если у вас интероп слой, то и используйте в нем uv_getaddrinfo_t* и маршаллить ничего не надо, сразу в сигнатуре параметра объявить

К сожалению, довольно много (в разы больше чем в скажем в мире JVM). Интеропа очень много и все те проавила описанные тут для него так же верны. Но очень часто скучающие программисты начинают что-то микрооптимимзировать через unsafe

А так еще unsafe для fixed size buffer иногда полезен

Хотелось бы, чтобы fixed-size buffers полностью ушли в прошлое и заменились inline arrays.

Как stackalloc стал доступен из safe

По текущему плану C# команды, даже если вы используете Span<> x = stackalloc но при этом у вас где-то (над методом или во всей сборке) указан skiplocalsinit, то всё выражение всё равно начнет требовать unsafe context.

В С/С++ ровно такой же каст вообще-то считается UB ;-)

а когда метод становится горячим (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р он не нужен и ему не надо ничего триммить

В .NET JIT нет автовекторизации

Это не правда. Вы имели ввиду авто-векторизацию циклов. И это не бесполезно, просто в дотнете на любой чих есть апишка под любой вид операции над массивом, и она уже обмазана симдами. Общей векторизации (ее еще называют 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 кодом.

SkipLocalsInit требует AllowUnsafeBlock, ваш код всё ещё unsafe.

вы же видите что у вас Error примерно равен разнице результатов?

Полагаю требуется некий контекст, вполне есть ситуации где можно гарантировать отсутствие как освобождения в 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).

1
23 ...

Информация

В рейтинге
737-й
Зарегистрирован
Активность