Start Debugging

performance

82 статей · Страница1из9

Итератор yield return или возврат List<T>: сколько на самом деле стоит метод с IEnumerable в C#

Метод с yield return стоит один 40-байтовый объект конечного автомата на вызов, сколько бы элементов он ни выдавал; List<T> сразу выделяет весь внутренний массив. Замеры на .NET 10.0.10: итератор выигрывает по памяти и при раннем выходе, List<T> выигрывает, когда вызывающий код перечисляет больше одного раза, нуждается в Count или вызывает ToList().

Цикл по ref T против индексации Span<T> в C#: почему версия со span не медленнее, а часто быстрее в .NET 10

В .NET 10 обычный цикл for по span[i] с границей span.Length компилируется в цикл без проверки границ, со снижением стоимости операций (strength reduction) и обратным отсчётом. Написанный вручную цикл с Unsafe.Add(ref r, i) и индексом int сохраняет расширение знака и сравнение при счёте вверх и в замерах оказался на 2-5% медленнее. Бенчмарки и дизассемблер JIT для arm64 и x64 на .NET 10.0.10.

Как синхронно обойти две коллекции без выделений памяти в куче в C#

Enumerable.Zip выделяет 144-208 байт на вызов и работает примерно в 10 раз медленнее обычного цикла. Для массивов и списков цикл for по двум span, обрезанным до одной длины, не выделяет память и работает быстрее всего; для синтаксиса foreach небольшой zip-перечислитель на ref struct отстаёт от него не более чем на 25%. Замеры на .NET 10.0.10 и .NET 11 RC 3.

Lazy<T> vs LazyInitializer.EnsureInitialized vs блокировка с двойной проверкой в C#

По умолчанию используйте Lazy<T>: он выполняет фабрику один раз, кеширует исключения, а чтение после инициализации слишком дёшево, чтобы его можно было измерить. LazyInitializer.EnsureInitialized подходит, когда вы лениво заполняете много полей или ячеек массива и не можете позволить себе лишний объект на каждое значение. Ручная блокировка с двойной проверкой на .NET 10 почти никогда не оправдана.

Что происходит с истёкшей записью MemoryCache и когда она на самом деле удаляется из памяти?

Истёкшая запись MemoryCache не удаляется по таймеру. Она остаётся в словаре, пока к ней не обратится чтение, не запустится фоновое сканирование с ограничением частоты или не начнётся уплотнение. Вот когда именно происходит каждое из этих событий и как добиться немедленного вытеснения.

Thread.Sleep, Task.Delay и SpinWait для ожидания в C#

Используйте await Task.Delay для любого ожидания по времени в асинхронном коде, Thread.Sleep только на выделенном потоке, которым вы владеете, а SpinWait только для ожиданий короче микросекунды внутри lock-free кода. Чтобы дождаться условия, применяйте сигнал (ManualResetEventSlim, SemaphoreSlim, TaskCompletionSource) вместо любого из этих трёх. Измерено на .NET 10.0.10.

Что такое скомпилированная модель в EF Core 11 и когда стоит её включать

Скомпилированная модель - это сгенерированный C#-код, который воссоздаёт вашу модель EF Core без запуска OnModelCreating. Замеры на EF Core 11 RC1: загрузка модели сокращается с 714 мс до 215 мс при 500 сущностях, но путь через MSBuild (заменивший EFOptimizeContext) медленнее, устаревшие модели ломаются молча, а фильтры запросов блокируют генерацию.

Что такое transparent-раскладка структуры и почему структура-обёртка с одним полем меняет соглашение о вызовах в .NET?

Transparent-структура - это структура, которую ABI обрабатывает точно так же, как её единственное поле. В .NET 11 такой гарантии нет: структура, оборачивающая double, передаётся в RCX в Windows x64, но в XMM0 в Linux и macOS. Здесь разобран дизассемблированный код JIT-компилятора .NET 11 для трёх ABI, ошибки P/Invoke, которые из этого возникают, и способы писать типы-обёртки, безопасные на любой границе.

AddDbContextPool или AddDbContextFactory для параллельного выполнения запросов EF Core

AddDbContextPool выдаёт один scoped DbContext на область DI, поэтому не может выполнять два запроса одновременно. AddDbContextFactory и AddPooledDbContextFactory дают контекст на каждый вызов, а именно это нужно параллельным запросам. Замеры на EF Core 11 RC 1: пулированная фабрика создаёт контекст за 342 нс и 40 Б против 17 мкс и 44 КБ.
2026-10-03 csharpdotnetasync

Как синхронно дождаться ValueTask в неасинхронном методе без выделения памяти

Сначала проверьте IsCompleted и прочитайте результат через GetAwaiter().GetResult(): это стоит 0 байт. Блокируйтесь только если ValueTask ещё не завершён, и делайте это через кешированное событие и UnsafeOnCompleted вместо AsTask(), который выделяет от 72 до 208 байт на вызов. Измерено на .NET 10 и .NET 11 RC1.
Вперёд