flang компилятор доказывает, что программа не зациклится 0.6.2 GitHub

Откат арены отдаёт куски системе: пик −36…61 % при времени ±1 %

Довод «удерживать куски выгодно, потому что арена остаётся горячей в кэше» стоял в fl_arena_rollback незамеренным. Замерили — довод верен по знаку и ничтожен по величине: отдача кусков системе стоит от −1,8 % до +1,2 % времени, а пик роняет на треть-две трети.

Отдаётся весь хвост цепочки за отметкой, кроме первых FL_ARENA_KEEP (= 4) кусков: их откат оставляет себе, потому что сразу после него fl_region_close кладёт копию результата обратно в арену, и без запаса эта копия идёт к malloc.

Чем подтверждено. Ветка b/arena-release над стволом 58ac015f. Обе колонки — ОДИН двоичный: perf на машине закрыт, а две сборки сравнивать нечестно, поэтому счётчики вшиты в тот же двоичный и включаются переменной среды. Прогоны подряд, пик через /usr/bin/time -v, загрузка машины названа у каждой пары.

flang check flang/self/types.flang --proof, загрузка 14,7–15,2:

что делает откатвремяпикпокупок у malloc
держит весь хвост (как было)1:34,926 236 536 КБ94 022
отдаёт весь хвост1:36,063 952 772 КБ1 303 680
отдаёт, оставив 1 кусок1:35,503 950 176 КБ1 104 760
отдаёт, оставив 4 куска1:34,993 974 552 КБ657 256

flang check flang/self/parser.flang --proof, загрузка 15,5–16,7:

что делает откатвремяпик
держит весь хвост1:42,923 022 784 КБ
отдаёт, оставив 41:43,761 172 216 КБ

То есть память −36,3 % и −61,2 %, время +0,07 % и +0,8 % при разбросе повторов 0,45 % по времени и 4,5 % по пику. Ведомость types.flang побайтово та же: 1 635 строк, одна сумма md5 на обе колонки.

И то же самое двумя обычными сборками, без счётчиков — счётчики стоят около процента сами по себе. Ствол против этой правки, подряд, загрузка 15,8:

прогонстволправка
check flang/self/types.flang --proof1:33,91 / 6 235 232 КБ1:33,37 / 3 965 748 КБ
check flang/self/parser.flang --proof1:42,92 / 3 022 784 КБ1:43,76 / 1 172 216 КБ
check flang/stdlib/strings.flang0:03,77 / 211 152 КБ0:04,03 / 89 064 КБ

Вывод у всех трёх побайтово тот же, коды возврата те же. На самом тяжёлом прогоне правка даже быстрее ствола на 0,6 %; дороже она только на мелком, и там это 0,26 с.

Пик — это МНОГО МЕЛКИХ кусков, а не несколько огромных. Счётчик по размерам на types.flang: из 94 022 купленных кусков 93 756 (94 % байт) — минимального размера FL_CHUNK_MIN = 64 КиБ. Отсюда и работал приём: отдавать надо было не редкие громадины, а поток обычных кусков.

Ещё два числа из того же счётчика. «Держим сейчас» в конце прогона совпадало с «куплено» до байта — то есть до этой правки арена не отдавала системе НИЧЕГО за весь прогон. И из 66 009 433 закрытий области 65 053 562 (98,6 %) отказывались по порогу «мало наросло», 731 972 — по обмеру, а откатов было 223 899: пик держат не отказы, а именно те двести тысяч откатов, после которых память оставалась у арены.

Чем ограничено. Мерили flang check на исходниках самого компилятора, а не перепечатку семени: перепечатка в этот час шла у соседней учётной записи, а второй такой машина не выдержала бы. Устройство арены у обеих команд одно, но доля отката в пике у перепечатки не измерена и может быть другой: по замеру 21 августа память печати лежит в глубокой нехвостовой рекурсии, где область отказывается от отката ЗАКОННО (результат больше четверти наросшего), а где отката нет — там и отдавать нечего.

Чего правка не трогает. fl_arena_reset по-прежнему оставляет куски себе: он зовётся между запросами оболочки, и там куски забирает тот же запрос сразу же.

Связано: arena-never-releases, arena-makes-memory-not-time-the-limit-of-a-long-computation, subgraphs-below-the-rollback-mark-need-no-copy, memory-peak-scales-with-obligations-not-file-size