Skip to content

Repository files navigation

OverflowProof

Замеры и проверки к статье «А чё, так можно было? int == -int» — что происходит на границах диапазона целых чисел и сколько стоит от этого защититься.

BenchmarkDotNet 0.15.8, Release, .NET 8, .NET 9 и .NET 10 в одном запуске, со снятием машинного кода.

Статья: «А чё, так можно было? int == -int»

Что проверяется

Раздел Что смотрим Что сравнивается
1 цена проверки переполнения сложение без проверки, с проверкой и в длинном типе
2 середина отрезка три способа, два из которых взяты из исходников .NET
3 модуль числа библиотечный, через ветвление и без ветвления
отчёт numbers границы диапазонов все знаковые типы: смена знака, модуль, деление на минус единицу
отчёт search двоичный поиск где ломается середина и чем это кончается

Стенд

Машина Процессор Система
Комп 1 Intel Core i9-10900KF 3.70GHz, 10 ядер Windows 10 22H2
Комп 2 AMD Ryzen 9 5950X 3.39GHz, 16 ядер Windows 10 1809
Комп 3 Intel Xeon W-2255 3.70GHz, 10 ядер Windows Server 2022
Комп 4 Intel Xeon Silver 4314 2.40GHz, 2 CPU, 32 ядра Windows Server 2022

Все машины x64. Выгрузки лежат в Results: Comp_1 — i9-10900KF, Comp_2 — Ryzen 9 5950X, Comp_3 — Xeon W-2255, Comp_4 — Xeon Silver 4314.

Результаты

Границы диапазонов

  тип           самое маленькое          -x равен x   модуль        x / -1

  sbyte                          -128          да    исключение          -128
  short                        -32768          да    исключение        -32768
  int                     -2147483648          да    исключение    исключение
  long           -9223372036854775808          да    исключение    исключение
  nint           -9223372036854775808          да    исключение    исключение

Одинаково на четырёх машинах и трёх рантаймах. У sbyte и short деление на минус единицу работает: перед вычислением они расширяются до int.

Цена проверки переполнения

Способ Комп 1 Комп 2 Комп 3 Комп 4
без проверки 2,521 2,599 2,928 4,749
с проверкой 2,600 2,409 3,057 4,951
в длинном типе 2,488 2,407 2,946 4,762

Микросекунды на 10 000 сложений, .NET 10. Разница от 3 до 4 процентов, а на Ryzen 9 5950X версия с проверкой оказалась быстрее.

Середина отрезка

Способ Комп 1 Комп 2 Комп 3 Комп 4
привычная запись 5,165 5,891 6,122 7,964
через разность 5,217 5,280 6,070 7,645
беззнаково 5,167 5,288 6,049 7,627

Наносекунды на четыре отрезка, .NET 10. Экономии одного вычитания, обещанной комментарием в SpanHelpers, в замере не видно: разница от 0,3 до 0,4 процента.

Модуль числа

Способ Комп 1 Комп 2 Комп 3 Комп 4
библиотечный 1,370 1,524 1,951 2,030
через ветвление 1,049 1,401 1,466 1,834
без ветвления 1,050 1,166 1,463 1,469

Микросекунды на 1024 числа, .NET 10. Проверка на переполнение внутри Math.Abs стоит от 9 до 38 процентов в зависимости от машины и способа сравнения.

Двоичный поиск

  low            high          привычно       разность     беззнаково

             0             10              5             5              5
             0     2000000000     1000000000    1000000000     1000000000
    1000000000     2000000000     -647483648    1500000000     1500000000
    1500000000     2000000000     -397483648    1750000000     1750000000
    2000000000     2147483647      -73741824    2073741823     2073741823
    2147483646     2147483647             -1    2147483646     2147483646

Поиск числа 2000000000 на отрезке от 0 до 2147483647:

  привычно      IndexOutOfRangeException: номер -536870912
  разность      найдено, шагов: 31
  беззнаково    найдено, шагов: 31

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

Нужны SDK .NET 8, 9 и 10: BenchmarkDotNet поднимает по процессу на рантайм.

dotnet --list-sdks

В пути к проекту не должно быть запятых и точек с запятой. BenchmarkDotNet собирает вспомогательный проект и передаёт путь в MSBuild без кавычек, тот разбирает эти знаки как разделители списка свойств и падает с MSB1006. Батник проверяет путь и останавливается сразу.

Весь прогон одной командой:

all.bat

Вручную, без скрипта:

dotnet run -c Release -f net10.0 -- checks
dotnet run -c Release -f net10.0 -- numbers
dotnet run -c Release -f net10.0 -- search
dotnet run -c Release -f net10.0 -- --filter *
dotnet run -c Release -f net10.0 -- --filter *CheckedBench*

Аргумент noasm выключает снятие машинного кода.

Причина любого сбоя записывается в Bdn\OverflowProof.log.

Как устроен замер

Все измеряемые методы лежат в Subjects.cs, у каждого NoInlining. Без этого компилятор посчитал бы результат при сборке: переполнение на константах он вычисляет сам и до выполнения дело не доходит. По той же причине числа берутся из массива, а не пишутся в коде.

Набор для сложения подобран так, чтобы сумма в int помещалась. Иначе вариант с проверкой бросал бы исключение, а вариант без проверки — нет, и сравнивалась бы не цена проверки, а цена обработки ошибки.

В замере модуля нет самого маленького числа: там сравнивается цена, а не поведение на границе. Поведение на границе проверяет отчёт numbers.

Двоичный поиск идёт по воображаемому массиву, где значение элемента равно его номеру: массива на два миллиарда элементов в памяти нет, а проверяется только арифметика границ. Отрицательный номер вместо чтения памяти бросает исключение.

Что проверялось и чем

Что проверялось Чем
Смена знака не меняет самое маленькое число отчёт checks сравнивает -x с x для int и long
Модуль такого числа не существует отчёт checks ловит исключение у библиотечного модуля
Свой модуль возвращает то же число отчёт checks сравнивает результат с исходным
Деление на минус единицу бросает исключение отчёт checks проверяет и деление, и остаток
Переполнение при сложении проходит молча отчёт checks сравнивает поведение с проверкой и без
Три способа середины расходятся только на больших границах отчёт checks и отчёт search на шести отрезках
Все три сложения дают одну сумму отчёт checks на наборе без переполнения
Поведение одинаково у всех знаковых типов отчёт numbers: sbyte, short, int, long, nint
Дело не в конкретной машине четыре машины, выгрузки всех четырёх в Results
Дело не в конкретной версии .NET 8, 9 и 10 в одном запуске

Все замеры сняты на x64 под Windows.

Дизассемблер на Linux требует установленного perf. Выключается аргументом noasm.

Отчёты

Bdn\results\                    отчёты BenchmarkDotNet: csv, md, html, машинный код
Bdn\OverflowProof.log           журнал прогона: причины сбоев только здесь
Results\Comp_N\
    checks_netN.0.txt           сверка, код возврата
    checks_netN.0_servergc.txt  то же на серверном сборщике
    numbers_netN.0.txt          границы диапазонов по всем знаковым типам
    search_netN.0.txt           середина отрезка и двоичный поиск
    probe_noasm.txt             проба на одном классе без дизассемблера
    probe_asm.txt               та же проба с дизассемблером
    bench\                      отчёты BenchmarkDotNet

Что где лежит

OverflowProof.csproj         многоцелевой: net8.0, net9.0, net10.0
OverflowProof.slnx
Program.cs                   точка входа, разбор аргументов
Subjects.cs                  все измеряемые методы
README.md
all.bat                      весь прогон одной командой
Benchmarks\
    CheckedBench.cs          раздел 1, цена проверки переполнения
    MidpointBench.cs         раздел 2, середина отрезка
    AbsBench.cs              раздел 3, модуль числа
Types\
    BenchmarkConfig.cs       три рантайма, дизассемблер, колонки и журнал
    Payloads.cs              наборы чисел и границы отрезков
Diagnostics\
    Checks.cs                сверка, код возврата
    Numbers.cs               границы диапазонов
    Search.cs                двоичный поиск
Results\
    Comp_1 .. Comp_4\        выгрузки прогона, по папке на машину

Ссылки

Границы

Числа в этом файле берутся только из отчётов.

Отчёты снимаются на x64. На другой разрядности nint займёт четыре байта, и его строка в отчёте numbers будет другой.

Ссылки на исходники .NET даны на тег v10.0.0, коммит зафиксирован: main уедет, и номера строк перестанут совпадать.

About

Что происходит на границах диапазона целых: минус не меняет int.MinValue, модуля у него нет, деление на -1 бросает исключение. Замеры на .NET 8/9/10, четыре машины.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages