Skip to content

Repository files navigation

BoundsCheckProof

Код к статье про устранение проверок границ массива в RyuJIT: шесть циклов, четыре рантайма (.NET 8/9/10/11), четыре машины. Один цикл JIT дожал в .NET 9, ещё один — в .NET 10, обратные циклы из issue 2017 года — в .NET 11, а цикл с i != Length таскает проверку до сих пор. Плюс два контроля, чтобы инструменту можно было верить.

Статья на Хабре: Бенчмаркая проверки границ: фикс, который шёл 8 лет, и проверка, которая жива до сих пор

Итог по всем циклам (наличие cmp/jae + RNGCHKFAIL в методе)

Код net8 net9 net10 net11 Пруф
for (i=0; i < len; i++) эталон
while ((uint)i < (uint)len) ЕСТЬ PR #100777 (jakobbotsch)
if (p.Len < s.Len) s[p.Len] ЕСТЬ ЕСТЬ PR #115980 (EgorBo)
for (i=len-1; i >= 0; i--) ЕСТЬ* ЕСТЬ* ЕСТЬ* #9505, с 2017
while (i != 0) src[--i] ЕСТЬ ЕСТЬ ЕСТЬ тред #84697
for (i=0; i != len; i++) ЕСТЬ ЕСТЬ ЕСТЬ ЕСТЬ #84697 (xtqqczze)

* у обратного цикла проверка на .net 8/9/10 живёт в запасной копии цикла (loop cloning): в бинарнике есть, времени не отнимает — платите размером кода. Контроли: FirstPlusLast — одна проверка вместо двух на любом рантайме; AtUnknownIndex — проверка везде, убрать нельзя.

Сводный трекер таких пропусков у команды JIT: #109677 (EgorBo). Картина совпала до инструкции на всех четырёх машинах (Ryzen 9 5950X, i9-10900KF, 2 x Xeon Silver 4314 x2).

Проверка границ по версиям .NET

Цена живой проверки i != len

Обход с конца — времени не отнимает

IV widening в .NET 9

Чем грозит и что делать

Цикл for (i = 0; i != len; i++) — единственный, переживший чистки вплоть до .NET 11: проверка на каждой итерации, на Xeon и i9 это стабильные +6–31% к горячему циклу. В горячем коде пишите канонический for (int i = 0; i < arr.Length; i++) по локальной переменной массива — его JIT гарантированно дожимает во всех рантаймах. Проверяется одной переменной окружения DOTNET_JitDisasm — snap.bat в комплекте.

Как воспроизвести

Дизасм (главный пруф). Нужны SDK .NET 8/9/10/11 в одном корне (установка — SETUP.md). Из папки Disasm/: snap.bat (Linux: bash snap.sh) → disasm_net8..11.txt, сверяй с таблицей выше. Готовые листинги с машины №3 лежат рядом.

Бенчмарк. Из корня: dotnet run -c Release -f net10.0. Четыре цикла × размеры 1К (L1) / 64К (L2) / 1М (L3) / 16М (RAM) × рантаймы 8/9/10. Прогон долгий — это нормально. .NET 11 в бенчмарке нет: BenchmarkDotNet 0.15.8 его пока не собирает (BDN #3017) — картина на 11 берётся из дизасма. После прогона в results/ появятся таблицы, сводка _RECAP.txt и графики _CHARTS.html.

Результаты моих прогонов на четырёх машинах — в Results/Comp_1..4, графики для статьи — в Docs/.

Файлы

  • Subjects.cs — все подопытные циклы, один источник на бенчмарк и дизасм
  • Benchmarks/BoundsCheckBench.cs — бенчмарк, 4 цикла × 4 размера × 3 рантайма
  • Disasm/ — снятие машинного кода + готовые листинги net8–net11
  • SETUP.md — установка SDK, включая .NET 11 preview

About

Проверки границ массива в RyuJIT: дизасм + бенчмарки на .NET 8/9/10/11

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages