Войти
ФлеймФорумПрограммирование

Разница между -O2 и -O3 в LLVM не превышает статистической погрешности

#0
(Правка: 21:50) 21:46, 10 дек 2021

- говорит дядька на ютьюбе.

https://youtu.be/r-TLSBdHe1A?t=1246

Коротко: некто Эмери Бергер написал утилиту, которая каждые пол-секунды переставляет программу в памяти, переставляя кучу, стек и функции местами. Утверждается, что из-за устройства кэша, бранч предиктора, TLB и прочих деталей процессора, одна только перестановка частей программы по разным адресам может дать до 40% ускорения или замедления. Если нас интересует эффект других действий на производительность - например, смена алгоритма или флаги кодогенерации - то тогда эффект от разницы в лейауте исказит наши результаты.

Например, мы можем думать, что программа стала быстрее от того, что мы повесили __noinline__ на функцию kukareku - а на самом деле, это оттого, что из-за смены формы кода у нас больше нет конфликта в бранч-предикторе и он больше не тормозит.

Утилита Бергера призвана решить эту проблему - прямо во время выполнения программы, она переставляет её части в памяти - тем самым прогоняя одну и ту же программу с разными лейаутами. Если выполонять программу в течение долгого времени, проверяя один и тот же код с разными данными и под разными лейаутами - получается целая статистика времён исполнения, которая выглядит, как нормальное распределение.

Тогда, если мы хотим измерить эффект какого-то конкретного действия (например, от одного флага компилятора) - мы собираем два экземпляра программы, собираем статистику по обоим - получается два гауссова колокола. Если мы сравним расстояние между центрами распределений с их дисперсией - то получим вероятностную оценку того, действительно ли это действие дало эффект на скорости работы, или же это нам просто так повезло с рандомом.

Используя этот метод, Бергер скомпилировал несколько бенчмарков шлангом c -O1, с -O2 и с -O3:

Speedup of -O2 over -O1 | Разница между -O2 и -O3 в LLVM не превышает статистической погрешности

Speedup of -O3 over -O2 | Разница между -O2 и -O3 в LLVM не превышает статистической погрешности

Speedup of -O3 over -O2 (magnified) | Разница между -O2 и -O3 в LLVM не превышает статистической погрешности

Is -O3 faster than -O2? | Разница между -O2 и -O3 в LLVM не превышает статистической погрешности

И теперь, если собрать все бенчмарки вместе и посмотреть на разницу между -O2 и -O3 и посчитать достоверность для неё - с вероятностью 26.4%, на самом деле разницы никакой нет и все различия получились по воле случая.

Поскольку даже в самых зашлёпных исследованиях принимают p-value не более 5% - то получается, что, согласно вот этому эксперименту, между -O2 и -O3 статистически значимой разницы нет.

А вы всё ещё верите в переполнение беззнаковых и стрикт алиасинг?

#1
21:57, 10 дек 2021

-Ofast -march=native он не пробовал замерять?

#2
22:46, 10 дек 2021

Открываешь
https://github.com/llvm/llvm-project/blob/main/llvm/lib/Transform… erBuilder.cpp

И грепаешь OptLevel > 1 и OptLevel > 2 (спойлер: различий не очень много)

kipar
> -Ofast
Оно же fast-math включает, там небось половина бенчмарков сломаются

#3
22:52, 10 дек 2021

Заодно можно всяких смешных комментов найти

  // Add TypeBasedAliasAnalysis before BasicAliasAnalysis so that
  // BasicAliasAnalysis wins if they disagree. This is intended to help
  // support "obvious" type-punning idioms.
  PM.add(createTypeBasedAAWrapperPass());
  PM.add(createScopedNoAliasAAWrapperPass());
#4
23:12, 10 дек 2021

return [](){};
> fast-math
Это который только на плавающего петуха влияет? Которого в значительной части программ почти что нет (если только это не числодробилки).

Имбирная Ведьмочка
> Утверждается, что из-за устройства кэша, бранч предиктора, TLB и прочих деталей процессора, одна только перестановка частей программы по разным адресам может дать до 40%
Разрабочики крестокомпиляторов пердолятся каждый раз с выискиванием ещё одного места в крестостандарте, которое бы позволило дать + 0.001% прироста производительности ценой обоссывания лица программиста, но толку от этого мало, ибо современные процессоры исполняют код одному лишь Ктулху известным способом, да так, что простая перестановка кода даёт в разы более заметный эффект.
Может стоит забить на все эти недооптимизации и вместо этого научиться расставлять код наиболее быстрым образом?

#5
(Правка: 23:38) 23:35, 10 дек 2021

Не, ну просто переставлять функции местами компиляторы и так умеют.

https://www.cism.ucl.ac.be/Equipements/Logiciels/Manuels/Intel/ic… /linux305.htm

The Intel C++ Compiler provides two methods of optimizing the layout of functions in the executable:

    1. use of a function order list
    2. use of -ipo

Each method has its advantages. A function order list, created with proforder, enables you to optimize the layout of non-static functions; that is, external and library functions whose names are exposed to the linker. The linker cannot directly affect the layout order for static functions because the names of these functions are not available in the object files.

On the other hand, using -ipo allows you to optimize the layout of all static or extern functions compiled with the Intel C++ Compiler. The compiler cannot affect the layout order for functions it does not compile, such as library functions. The function layout optimization is performed automatically when IPO is active.

#6
0:56, 11 дек 2021

Имбирная Ведьмочка
> огласно вот этому эксперименту, между -O2 и -O3 статистически значимой разницы
> нет.
ни чего, что это уже давно известно. Это далеко не первый тест.

#7
3:24, 11 дек 2021

Мне казалось, что -O3 включает автовекторизацию. Соответственно, на коде, который допускает таковую, выигрыш существенный. В этом списке программ я таковых почти не вижу (если только h264, но там, небось, ручной ассемблер во всех векторизуемых местах).

#8
4:38, 11 дек 2021

Panzerschrek[CN]
> простая перестановка кода даёт в разы более заметный эффект.
Надеюсь, что переставлял выровненный код функций программы?

т.к. простое выравнивание кода очень сильно сказывается на его производительности исполнения процессором.

#9
5:24, 11 дек 2021

}:+()___ [Smile]
> Мне казалось, что -O3 включает автовекторизацию.

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

#10
(Правка: 9:08) 9:08, 11 дек 2021

=A=L=X=
Тот случай , когда  стоит доверять своей интуиции.

#11
13:25, 11 дек 2021
  if (OptLevel > 2)
    MPM.add(createAggressiveInstCombinerPass());
  if (OptLevel > 2)
    MPM.add(createCallSiteSplittingPass());
  if (OptLevel > 2 && EnableFunctionSpecialization)
    MPM.add(createFunctionSpecializationPass());
  if (OptLevel > 2)
    MPM.add(createArgumentPromotionPass()); // Scalarize uninlined fn args
PM.add(createLoopUnswitchPass(SizeLevel || OptLevel < 3, DivergentTarget));

https://github.com/llvm/llvm-project/blob/54e21df973e154052e74a45… Pass.cpp#L195

  UP.Threshold =
      OptLevel > 2 ? UnrollThresholdAggressive : UnrollThresholdDefault;

Это все, что включает O3 относительно O2

ФлеймФорумПрограммирование