Составляют ли компиляторы C код дублирования (слияния)?

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

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

Мне интересно, потому что в C есть библиотеки с заголовком *, которые могут добавить много дублированного кода, поэтому было бы полезно знать, могут ли некоторые компиляторы C обнаружить это и обработать его более эффективно.

* В заголовке-only-library я имею в виду заголовок, который определяет код напрямую, а не определения функций.

И если это будет сделано, было бы полезно знать, в каких условиях и ограничениях применяются, чтобы обеспечить его использование.

Примечание (для целей вопроса - любой популярный компилятор C - это GCC/Clang/Intel/MSVC).

Библиотека только для заголовка, которую я нашел, называется uthash, использует некоторые очень большие макросы, и я хотел знать, происходит ли какая-то компиляция, которая могла бы умно дедуплировать такие огромные блоки кода, см. например: uthash.h, другой похожий пример inline qsort.h


Пример блока, который можно было бы дедуплицировать (оказывается, Py_DECREF может расширяться в довольно большой блок кода).

#define PY_ADD_TO_DICT(dict, i)  \
    do {
        PyDict_SetItemString(dict, names[i], item = PyUnicode_FromString(values[i])); \
        Py_DECREF(item); \
    } while (0)

/* this could be made into a loop */
PY_ADD_TO_DICT(d, 0);
PY_ADD_TO_DICT(d, 1);
PY_ADD_TO_DICT(d, 2);
PY_ADD_TO_DICT(d, 3);

Обратите внимание, что это надуманно, но на основе реального примера.


Разъяснение

Кажется, что короткий ответ на мой вопрос - нет (или только в некоторых ограниченных/тривиальных случаях), просто чтобы выяснить, почему я спрашивал немного дальше.

Некоторые ответы в комментариях, по-видимому, предполагают, что вы просто переработаете код в функцию.
Это почти всегда лучший вариант, тем не менее, есть моменты, когда могут отображаться блоки очень похожего кода.

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

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

Ответы

Ответ 1

В зависимости от инструментальной цепочки у вас могут быть варианты тренировки компилятора и компоновщика в распознавании и объединении избыточного кода. Некоторые хорошие ключевые слова google включают:

Обратите внимание, что страница gcc optimizations, упомянутая в предыдущих комментариях, содержит некоторые интересующие флаги, а именно:

  • -ftree-хвост-слияние
  • -ftree-переключатель преобразования
  • -fgcse
  • -fcrossjumping
  • -fipa-PTA
  • -fipa-icf (идентичное сложение кода), добавлено в GCC5.x
  • -fopa-ф
  • -flto
  • -fwhole-программа

Наконец, эти сообщения в блогах информативны:

Ответ 2

Что может быть самым простым способом описать это, называется кодом рефакторинга. Это то, что вы могли бы сделать вручную, как разработчик, но это не особенность современных компиляторов и оптимизаторов C, по крайней мере, до того, как GCC подключен. Ниже приведен список всех индивидуальных оптимизаций, которые вы можете установить (например, через -O0 и -O2) в GCC, и ни один из них не реорганизует ряд подобных операторов для использования общего индекса в цикле.

 Optimization Level Zero              Optimization Level Two
 ============================================================================================================
 -fauto-inc-dec                       -fthread-jumps 
 -fbranch-count-reg                   -falign-functions  -falign-jumps 
 -fcombine-stack-adjustments          -falign-loops  -falign-labels 
 -fcompare-elim                       -fcaller-saves 
 -fcprop-registers                    -fcrossjumping 
 -fdce                                -fcse-follow-jumps  -fcse-skip-blocks 
 -fdefer-pop                          -fdelete-null-pointer-checks 
 -fdelayed-branch                     -fdevirtualize -fdevirtualize-speculatively 
 -fdse                                -fexpensive-optimizations 
 -fforward-propagate                  -fgcse  -fgcse-lm  
 -fguess-branch-probability           -fhoist-adjacent-loads 
 -fif-conversion2                     -finline-small-functions 
 -fif-conversion                      -findirect-inlining 
 -finline-functions-called-once       -fipa-cp 
 -fipa-pure-const                     -fipa-sra 
 -fipa-profile                        -fisolate-erroneous-paths-dereference 
 -fipa-reference                      -foptimize-sibling-calls 
 -fmerge-constants                    -foptimize-strlen 
 -fmove-loop-invariants               -fpartial-inlining 
 -fshrink-wrap                        -fpeephole2 
 -fsplit-wide-types                   -freorder-blocks -freorder-blocks-and-partition -freorder-functions 
 -ftree-bit-ccp                       -frerun-cse-after-loop  
 -ftree-ccp                           -fsched-interblock  -fsched-spec 
 -fssa-phiopt                         -fschedule-insns  -fschedule-insns2 
 -ftree-ch                            -fstrict-aliasing -fstrict-overflow 
 -ftree-copy-prop                     -ftree-builtin-call-dce 
 -ftree-copyrename                    -ftree-switch-conversion -ftree-tail-merge 
 -ftree-dce                           -ftree-pre 
 -ftree-dominator-opts                -ftree-vrp 
 -ftree-dse                           -fuse-caller-save
 -ftree-forwprop                
 -ftree-fre                     
 -ftree-phiprop                 
 -ftree-sink                    
 -ftree-slsr                    
 -ftree-sra                     
 -ftree-pta                     
 -ftree-ter                     
 -funit-at-a-time               

Ссылки


Ответ 3

Хотя это, кажется, очень выполнимо, я никогда не видел никакой оптимизации, которая займет два одинаковых фрагмента кода и объединит их в один цикл.

Однако есть два случая дублирования, которые удаляются компилятором (наиболее очевидно, потому что также есть увеличение производительности, а не только увеличение размера кода):

  • Когда два выражения оцениваются в одном и том же значении, в той же функции метод общий отказ подвыражения удаляет избыточные вычисления.
    Интересно, что с агрессивной встраиванием и оптимизацией времени соединения все больше и больше случаев может быть покрыто этим, но, конечно, inlining имеет свои суровые бинарные размеры.

  • Когда можно векторизовать код, векторный компилятор может часто идентифицировать это и делать для вас векторизацию.

Кроме этого вам придется вручную реорганизовать код, чтобы удалить дублирование.