Nadav Amit, разработчик ядра Linux из компании VMware, поделился (https://nadav.amit.zone/blog/linux-inline) результатом исследования особенностей оптимизации в GCC небольших функций ядра. Исследование было проведено после того, как разработчик столкнулся с непонятным феноменом - внесение несущественных изменений в код ядра, приводило к небольшому, но заметному снижению производительности в тестах. Примечательно, что подобные вносимые изменения были оптимизациями и теоретически должны были увеличить производительность, но на деле производительность падала.
Дело оказалось в том, что GCC принимает решение об использовании inline-развёртывания функций в зависимости от результатов косвенной оценки размера результирующего кода (даже если функция определена с ключевым словом "inline"). Компилятор не учитывает фактический размер результирующего кода, а пытается прогнозировать его. Для ассемблерных вставок прогнозирование делается на основе числа переводов строк ("\n") и разделителей (";") в исходном тексте.
Логика подобного расчёта связана с допуском, что одна строка представляет собой одну ассемблерную инструкцию. Но при этом не учитываются ситуации, когда в ассемблерных вставках применяется большое число директив для вычислений, несколько строк которых в результате приводят к генерации лишь одной небольшой инструкции. Подобная особенность может приводить к таким казусам как снижение производительности при добавлении несущественных макросов. Некоторые разработчики отмечают (https://news.ycombinator.com/item?id=18170499) влияние на inline-развёртывание в новых версиях GCC даже замены символов табуляции на пробелы.
Разница в производительности особенно бросается в глаза для функций с ассемблерными вставками, для которых добавляется излишняя обёртка вместо прямой подстановки нескольких указанных в функции инструкций. В ядре Linux проблему представляют компактные функции с макросами WARN(), для которых оказалось, что не выполняется ожидаемая inline-оптимизация.
Автор исследования делает вывод, что требуется создание новых средств для контроля за поведением расширенных возможностей компилятора и анализа эффективности результирующих исполняемых файлов. В настоящее время единственным способом убедиться, что код сгенерирован именно так как рассчитывал разработчик остаётся ручное инспектирование итоговых машинных инструкций.URL: https://news.ycombinator.com/item?id=18169584
Новость: https://www.opennet.ru/opennews/art.shtml?num=49412
>Дело оказалось в том, что GCC принимает решение об использовании inline-развёртывания функций в зависимости от результатов косвенной оценки размера результирующего кода (даже если функция определена с ключевым словом "inline").GCC теперь Apple пишет? "Мы знаем лучше что вам надо"?
inline _всегда_ был _хинтом_ компилятору, что вот это вот имеет смысл попробовать сделать inline, а не директивным указанием. Точно так же как пресловутый register в определении переменных.> GCC теперь Apple пишет? "Мы знаем лучше что вам надо"?
apple занят, он пишет llvm, и у него все хорошо с инлайнами (кроме несовместимости очень старого gcc кода). Проблема гнутых обезьянок в том, что они как раз не знают лучше, и добавили нечеловеческой логики в это незнание.
> сделать inline, а не директивным указанием. Точно так же как пресловутый
> register в определении переменных.Register так вообще считается deprecated, в плюсах им вообще пользоваться не следует. Потому что пытаться хинтить компилятору аллокацию регистров - дело тухлое. А если ну очень хочется, есть asm() и intrinsics, наконец. В остальных случаях компилятор и сам догадывается как регистры раскинуть, так что втыкание "register" ничего не дает кроме лишней писанины.
>> GCC теперь Apple пишет? "Мы знаем лучше что вам надо"?
> apple занят, он пишет llvm, и у него все хорошо с инлайнамиЗато у него почему-то хваленая скорость компиляции ушла в плинтус как только им захотелось производительность кода сколь-нибудь сравнимую с gcc.
> и добавили нечеловеческой логики в это незнание.
...а разработчики шланга собезьянили, как обычно?
> Потому что пытаться хинтить компилятору аллокацию регистров - дело тухлое.смотря на какой архитектуре.
> В остальных случаях компилятор и сам догадывается как регистры раскинуть,
"в остальных случаях компилятор и сам догадается, что заинлайнить". Вот он и "догадался".
>сам догадываетсяя проверял, у меня он догадывался на простом счётчике для перебора массива только после компиляции с инфой профилирования (pgo), разница производительности что-то там в районе 3-4 порядков была. Просто космос. Добиться такого же результата позволяло добавление register, поэтому говорить можно что угодно.
> Register так вообще считается deprecated, в плюсах им вообще пользоваться не следует.Да-да. Давеча вот такое видел от GCC 8:
warning: ISO C++17 does not allow ‘register’ storage class specifier [-Wregister]
inline был хинтом компилятору только в расширениях gnu89. В c99 ввели понятие inline, и оно с тех пор не связано с инлайнингом напрямую.
Это зачатки ИИ :-) , в среднем хорошо но в отдельных местах...
... и если приблизить, то мы увидим 12 новых if.
можно делать __attribute__((always_inline)), если требуется всегда inline.[i]настоящее время единственным способом убедиться, что код сгенерирован именно так как рассчитывал разработчик остаётся ручное инспектирование итоговых машинных инструкций. [/i] - единственный способ понять, правильно ли сгенерировалось - посмотреть асм листинг. по моему опыту значительная часть разработчиков не будут читать листинг.
автор статьи там написал, почему always_inline - не решение проблемы. Но вы, конечно, ее не читали
читал.
Вы не читали или не разобрались в сути вопроса.
случаи там рассмотренные вполне можно было бы решить с помощью always_inline.
рассматривался допустим
static inline void *kzalloc(size_t size, gfp_t flags)
который вызывает kmalloc, который по заверениям always_inline, и результирующий код не дает компилятору дважды выполнить подстановку кода.
можно было бы бахнуть always_inline и выполнило бы подстановку дважды.то что функции-обертки волшебным образом не наследуют атрибуты или какие-то свойства пожалуй известно всем.
А вы так усердно читали, что мысленно вписали в статью того, чего там нет. :)
Нужно новое ключевое слово really_inline.
[i]Нужно новое ключевое слово really_inline.[/i] - кому нужно-то? Вы можете сделать inline силой с помощью атрибута. только в 9 случаях из 10 это не нужно.
__attribute__((always_inline))
Нестандартненько.
в_натуре(__inline__):)
без_базара(--inline__)
"примечательно", что этот пакистанец, кажется, на самом деле тестировал свои оптимизации на предмет реально ли они оптимизируют.
Вот это - действительно неожиданный ход.
> "примечательно", что этот пакистанец, кажется, на самом деле тестировал свои оптимизации
> на предмет реально ли они оптимизируют.
> Вот это - действительно неожиданный ход.теперь я знаю чем пакистан от индии отличаются, раньше я их не различал
> теперь я знаю чем пакистан от индии отличаются, раньше я их не
> различалИндусы рисуют красную точку на лбу, а паки проверяют оптимизацию компилятора.
> теперь я знаю чем пакистан от индии отличаются, раньше я их не
> различалв Индии помимо Кумаров тоже встречаются всякие Джавахарлалы, первые делают тупенько, за вторыми замучаешься распутывать, что они там наоптимизировали и почему ты одну строчку добавил а оно стало медленнее в сто раз. Хз, на самом деле, что хуже.
видимо, зависит от сорта плана, который растет именно в этой местности.
Израиль не Пакистан. А мужичек сам в штатах.
"
Влияние числа переводов строк и разделителей в исходном тексте программ на быстродействие скомпилированного кода
"Эпично.
ИИ в компиляторах...
> Эпично.Вы явно недооцениваете "взрывно-поджигательный" потенциал предложения:
> "влияние на inline-развёртывание в новых версиях GCC даже замены символов табуляции на пробелы."
> identical functions that used spaces instead of tabs could run significantly slower because they weren't inlined.в любом сра^W обсуждении на опеннете новости о питоне (или чем-то питоноподобном), где примерно 2/3 комментариев рассматривают (не)допустимость ущемления прав разработчика на свободное оформление кода, глубинную (не)правильность влияния пробелов на семантику ЯП (и общее влияние на интеллект, сексуальные пристрастия или некоторые анатомические детали, вроде кривизны или места крепления верхних конечностей).
> глубинную (не)правильность влияния пробелов на семантику ЯПНа семантику оно и не влияет.
>> глубинную (не)правильность влияния пробелов на семантику ЯП
> На семантику оно и не влияет.Даже если бы и не влияло (что не так[0]) -- таки уп0pно обуждают.
Из недавнего: https://www.opennet.ru/openforum/vsluhforumID3/115403.html#63[0]
while True:
...
if something < X:
result.add(Y)
break
+1 Tab
while True:
...
if something < X:
result.add(Y)
break
При чем здесь питон? Речь про C/C++.
> Вы явно недооцениваете "взрывно-поджигательный" потенциал предложения:
>> "влияние на inline-развёртывание в новых версиях GCC даже замены символов табуляции на пробелы."private\windows\media\avi\verinfo.16\verinfo.h:
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !!!!!!!IF YOU CHANGE TABS TO SPACES, YOU WILL BE KILLED!!!!!!!
* !!!!!!!!!!!!!!DOING SO [ВЦ.] THE BUILD PROCESS!!!!!!!!!!!!!!!!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Мне вот интересно, какому "умному" человеку пришло в голову внести в gcc т.н. GNU Extensions, которые разрешают, например, указывать размер статического (Карл!) массива в рантайме... Кому это было нужно? Ниасиляторам С и плюсов?
Тому, кто потом в Комитете принимает решения по следующему Стандарту, куда расширения языка постепенно включаются.
Приведенный пример с массивами - это не расширение языка, это радикальное изменение.
Это расширение только для С++, для С99 это стандарт.
А в C11 опять VLA не одобряэ... Путаники-с.
не статического массива а массива на стеке, дятел. Разница в том что это довольно просто реализовать, удобно, и не ломает язык. И да, это входит в c99 сейчас, наверняка войдет и в c++2x
> не статического массива а массива на стеке, дятел.От дятла слышу. Всю жизнь использовали в повседневной речи - "статический" и продолжают использовать.
> это довольно просто реализовать, удобно, и не ломает язык
Это может привести к undefined behavior если попытка выделения памяти окончилась фейлом.
ПОловина софта не проверяет даже malloс на NULL...
Любое использование стека может привести к UB. На деле, кому надо обрабатывают SIGSEGV, а остальным плевать
> Это может привести к undefined behavior если попытка выделения памяти окончилась фейлом.Это справедливо также если размер массива определяется во время компиляции. Компилятор то не знает, сколько там во время выполнения реально свободного стека останется. Будет такое "статическое" undefined behavior. :)
> Всю жизнь использовали в повседневной речи - "статический" и продолжают использовать."Объекты c static storage duration инициализируются до program startup."
> Всю жизнь использовали в повседневной речи - "статический" и продолжают использовать.Игнорируя, что в общепринятом смысле "динамичность"/"статичность" относятся не к изменяемости/константности размера объекта, а к типу его жизненного цикла, которых не джва, а три: "динамические" (живут неоговоренное время на куче), "статические" (живут всё время исполнения программы в заранее отведённой памяти) и "автоматические" (живут в кадре стэка (* храниться могут и в регистрах процессора, а не непосредственно в ОЗУ) и к "статическим" таки не относятся).
Не "игнорируя", а просто используя в определенном, заранее подразумеваемом контексте.
Кем подразумеваемом? В каком контексте? "Статический массив" - вполне допустимое выражение, которое значит совсем не то, что "массив на стеке".
> наверняка войдет и в c++2xОно не войдет в C++ никогда, ибо ломает систему типов рядом с темплейтами.
> Мне вот интересно, какому "умному" человеку пришло в голову внести в gcc
> т.н. GNU Extensions, которые разрешают, например, указывать размер статического (Карл!)
> массива в рантайме... Кому это было нужно? Ниасиляторам С и плюсов?Вообще-то это часть стандарта C ... с 99 года аж. Туда же и всякие variable args и проч. Полезные между прочим штуки. И раздуплятся ли стандартизаторы - черт его знает, но все этим пользуются. Тот же шланг этому тоже научился. И без них иногда как-то душно.
>черт его знает, но все этим пользуются.Некоторые уже напользовались -- избавиться никак не могут.
Старик Крупский, вот например, неполиткорректно против.
https://lwn.net/Articles/749064/
https://lwn.net/Articles/763641/
https://lwn.net/Articles/764325/
> Тот же шланг этому тоже научился. И без них иногда как-тоВы с "прогрессивным" человечеством, я вижу, на защите чести и свобод?
Надо успеть, пока супостат не вернулся с "ретрита".
> душно.
> В настоящее время единственным способом убедиться, что код сгенерирован именно так как рассчитывал разработчик остаётся ручное инспектирование итоговых машинных инструкций.Ну так одно другому не мешает и бенмарки неплохо бы иметь. А то выйдет новая-кленовая версия компилятора с другим эвристиками и снова все просядет... или нет.
Теория:-O turns on the following optimization flags:
...
-fomit-frame-pointerПрактика:
0xffffffff817929e0 <+0>: push %rbp
...
0xffffffff817929ee <+14>: pop %rbp
0xffffffff817929ef <+15>: retq
Данной оптимизация отключена, поскольку обратная опция находится в зависимостях других опций вроде C++ исключений. Без которых GCC не может сгенерировать рабочий код. Доходит до абсурда когда stack-frame создаётся в абсолютно пустых функциях.
> Данной оптимизация отключена, поскольку обратная опция находится в зависимостях других
> опций вроде C++ исключений. Без которых GCC не может сгенерировать рабочий
> код.По поводу "других" в документации сказано: Omit the frame pointer in functions that don't need one.
make V=1 показывает, что fomit-frame-pointer передаётся компилятору.
Пример навскидку.
void cyc2ns_read_begin(struct cyc2ns_data *data)
{
int seq, idx;preempt_disable_notrace();
do {
seq = this_cpu_read(cyc2ns.seq.sequence);
idx = seq & 1;data->cyc2ns_offset = this_cpu_read(cyc2ns.data[idx].cyc2ns_offset);
data->cyc2ns_mul = this_cpu_read(cyc2ns.data[idx].cyc2ns_mul);
data->cyc2ns_shift = this_cpu_read(cyc2ns.data[idx].cyc2ns_shift);} while (unlikely(seq != this_cpu_read(cyc2ns.seq.sequence)));
}void cyc2ns_read_end(void)
{
preempt_enable_notrace();
}
Прошу прощения, если кому непривычен формат,
отсутствие фреймов видно:
.text:0000000000000228 cyc2ns_read_begin proc near
.text:0000000000000228 inc gs:__preempt_count
.text:000000000000022F mov rsi, offset cyc2ns
.text:0000000000000236 loc_236:
.text:0000000000000236 mov edx, dword ptr gs:cyc2ns+20h
.text:000000000000023D mov eax, edx
.text:000000000000023F and eax, 1
.text:0000000000000242 shl rax, 4
.text:0000000000000246 add rax, rsi
.text:0000000000000249 mov rcx, gs:[rax+8]
.text:000000000000024E mov [rdi+8], rcx
.text:0000000000000252 mov ecx, gs:[rax]
.text:0000000000000255 mov [rdi], ecx
.text:0000000000000257 mov eax, gs:[rax+4]
.text:000000000000025B mov [rdi+4], eax
.text:000000000000025E mov eax, dword ptr gs:cyc2ns+20h
.text:0000000000000265 cmp edx, eax
.text:0000000000000267 jnz short loc_236
.text:0000000000000269 retn
.text:0000000000000269 cyc2ns_read_begin endp
.text:000000000000026A cyc2ns_read_end proc near
.text:000000000000026A dec gs:__preempt_count
.text:0000000000000271 jnz short locret_278
.text:0000000000000273 call ___preempt_schedule_notrace
.text:0000000000000278 locret_278:
.text:0000000000000278 retn
.text:0000000000000278 cyc2ns_read_end endp
.text:0000000000000278
Можно предположить, что собирали там с опцией CONFIG_FRAME_POINTER.В таком случае, возникает вопрос: влияет ли она на эффект от inline?
grep -C2 frame-pointer MakefileHOSTCFLAGS := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
-fomit-frame-pointer -std=gnu89 $(HOST_LFS_CFLAGS)--
ifdef CONFIG_FRAME_POINTER
KBUILD_CFLAGS += -fno-omit-frame-pointer -fno-optimize-sibling-calls
else
# Some targets (ARM with Thumb2, for example), can't be built with frame
# pointers. For those, we don't have FUNCTION_TRACER automatically
# select FRAME_POINTER. However, FUNCTION_TRACER adds -pg, and this is
# incompatible with -fomit-frame-pointer with current GCC, so we don't use
# -fomit-frame-pointer with FUNCTION_TRACER.
ifndef CONFIG_FUNCTION_TRACER
KBUILD_CFLAGS += -fomit-frame-pointer
endif
endif
push %rbp и pop %rbp не имеют ни какого отношения к стеку, это просто сохранение регистра для использования в вычислениях функции. Раньше rbp использовался для буфера в стеке, теперь нет.
> push %rbp и pop %rbp не имеют ни какого отношения к стеку,
> это просто сохранение регистра для использования в вычислениях функции. Раньше rbp
> использовался для буфера в стеке, теперь нет.Вот подпрограмма целиком:
0xffffffff817929e0 <+0>: push %rbp
0xffffffff817929e1 <+1>: mov $0x14080c0,%esi
0xffffffff817929e6 <+6>: mov %rsp,%rbp
0xffffffff817929e9 <+9>: callq 0xffffffff8125d590 <__kmalloc>
0xffffffff817929ee <+14>: pop %rbp
0xffffffff817929ef <+15>: retq
mov %rsp,%rbp — формирование фрейма, который не используется.
И эти люди ещё ругают MSVC за изначальный отказ от inline-инструкций в x86_64 как класса =)
> И эти люди ещё ругают MSVC за изначальный отказ от inline-инструкций в
> x86_64 как класса =)Дык msvc и C99 не особо поддерживает. Там народ недавно нашел брутальные факапы с этим даже в распоследней студии, которая выдает совершенно левые варнинги на валидный код. В общем, студия у ms тоже сдулась. Наверное мы по мнению MS должны в ядра операционок и прочие микроконтроллеры 17-е плюсы пихать, или дотнет. Главное не забудьте потом ремни пристегнуть - иначе по асфальту такая фирмварь вас все же размажет...
Кто все эти люди, что это за язык? Ни одного слова не понял ни из комментов, ни из статьи. Чьто это? Где я?)
Это GCC, братан
Ну раз речь о ядре Линукса, то догадайся)
> Ну раз речь о ядре Линукса, то догадайся)Кто ж его знает -- может у него сейчас 2088, ядро в третий раз переписали, в GCC древний Си заменили на архаичный Оксид (а плюсы вообще исключили из набора, т.к c++73 и выше может разоборать только ИИ, с помощью машины Типпетта-Цанга умеющий передавать информацию самому себе в прошлое)?
Assembly Language
GCC – GNU Compiler Collection
Про инлайнинг сами попгуглите.
GNU C Compiler = GCC.
Не путать с GNU Compiler Collection, такого языка программирования нет.
немного не в тему, но может кто подскажет, такая проблемка: использую arm-none-eabi-gcc-7.2.1 с cmake , и результаты оптимизации как-то сильно зависят от cmake кэша, процентов на 10 размер бинарника разный выходит, при том что реально разные варианты кода в пределах тех же самых функций получаются, проверял ассемлерные дампы. Пока лечится стиранием директории CMakeFiles, но может есть более умное решение?
Не знаю точно, но может быть поможет export LDFLAGS='-s'
чтобы вырезалась (strip) служебная информация из конечного результата.
нет, дело не в этом, я сравниваю дампы и размер бинарника уже после strip. На функцинальность эта проблема особо не влияет, но пока работаю над кодом в течении дня к бинарнику несколько кб мусора приростает. Приходится каждый раз когда нужно узнать точный размер или коллегам прошивку передать стирать CMakeFiles и пересобирать начисто.
включаешь verbose и смотришь опции компиляции или включаешь -frecord-gcc-switches и смотришь в бинарнике до стрипа.
Линус недавно устроил голвомойку разработчикам GCC за глюки в версии 4.9, кажется. Похоже, пора устроить её снова!
А то что? На шланг перейдёт? Ну наконец-то в ядро примут патчи совместимости со шлангом.
Или clang достигнет критического уровня совместимости с расширениями gcc.
Собственно, уже большая часть кода ядра прекрасно собирается.
> Собственно, уже большая часть кода ядра прекрасно собирается."Немножечко беременна".
Скорее бы Linux портировали на Clang/LLVM.
Нет, уж лучше старые глюки, чем новые.
> Скорее бы Linux портировали на Clang/LLVM.Мечтаешь увидеть проприерасские SDK к железкам, где даже сорц компилера зажали? Так что потом под эту архитектуру будет только какой-то левый блоб, под какой-нибудь 32-битный рхел 6, потому что у разработчиков видите ли было вот это, а если вы не похожи на 32-битный 6-й рхел... то черт его знает, виртуалочку чтоли запустите для пересборки, все дела, мде?
> Некоторые разработчики отмечают влияние на inline-развёртывание в новых версиях GCC даже замены символов табуляции на пробелы.При чем тут gcc вообще? Коммент по ссылке про v8 (движок JavaScript).
> При чем тут gcc вообще?Там речь про GCC v8.
> Коммент по ссылке про v8 (движок JavaScript).
А в этом же комментарии упоминание v5.9 тогда про какой движок и откуда в JavaScript-движке "inline assembly"?
Web Assembly? Он вроде позваляет на любом языке писать.
Web Assembly в Linux kernel?
Да. https://github.com/rianhunter/wasmjit
JavaScript движок под названием V8, версия движка 5.9. Что не понятно?
> откуда в JavaScript-движке "inline assembly"?V8 генерирует машинный код. При этом часть функций инлайнится, так же как и при компиляции из других языков. Решение о том, инлайнить код или нет, судя по комментарию, принимается с учетом пробелов.
Удивиился, что не Фороникс,
https://lwn.net/Articles/767884/rss
обнаружив сабж на.Бывает же?!