Могут ли современные компиляторы развертывать циклы for, выраженные с помощью итераторов начала и конца?

Рассмотрим следующий код

 vector<double> v;
 // fill v
 const vector<double>::iterator end =v.end();
 for(vector<double>::iterator i = v.bgin(); i != end; ++i) {
   // do stuff
 }

Могут ли такие компиляторы, как g++, clang++, icc разворачивать такие циклы. К сожалению, я не знаю ассемблера, чтобы проверить на выходе, разворачивается ли цикл или нет. (и у меня есть доступ только к g++.)

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

Спасибо за ваши ответы, и прежде чем некоторые из вас начнут читать лекции о преждевременной оптимизации, это упражнение на любопытство.


person san    schedule 17.07.2012    source источник
comment
Поскольку end является динамической величиной, вы можете сделать не так уж много развертывания...   -  person Kerrek SB    schedule 17.07.2012
comment
Я предполагаю, что развертывание не требуется, поскольку размер не может быть предсказан.   -  person    schedule 17.07.2012
comment
+1 за превентивную защиту от обвинений в преждевременной оптимизации :-)   -  person Aasmund Eldhuset    schedule 17.07.2012
comment
Определенно можно развернуться, не зная количества поездок. Просто требуется немного кода для очистки... Я делал это много раз раньше.   -  person Mysticial    schedule 17.07.2012
comment
@Kerrek SB Я думаю, что end не является динамическим, я определяю его вне цикла как константу.   -  person san    schedule 17.07.2012
comment
Может, конечно, но будет? Я не знаю, поэтому я просто собираюсь попробовать.   -  person harold    schedule 17.07.2012
comment
@harold Спасибо за это. Как я уже сказал, я не знаю, как читать сгенерированную сборку, чтобы понять, насколько хорошо она работает.   -  person san    schedule 17.07.2012
comment
@san Вы определяете это как const, а не как константу. Его значение определяется во время выполнения, а не во время компиляции, поэтому постоянство нельзя использовать для оптимизации/развертывания цикла.   -  person Jonathan Grynspan    schedule 17.07.2012
comment
@JonathanGrynspan Понятно. Это действительно не константа времени компиляции. Я надеялся, что теперь компилятор будет знать, что положение end внутри тела цикла не меняется, и использовать эту информацию. Это отличается от определения цикла с помощью for(..; i !=v.end(); ++i)   -  person san    schedule 17.07.2012
comment
MSVC не разворачивал этот цикл ни в одном из моих тестов (полный уровень оптимизации, предпочтение скорости и fp:fast)   -  person harold    schedule 17.07.2012
comment
@harold Не могли бы вы добавить это как ответ, чтобы я мог проголосовать за него.   -  person san    schedule 17.07.2012
comment
@san: То, что VC ++ не работает, не означает, что этого не делает ни один другой компилятор. ;-]   -  person ildjarn    schedule 17.07.2012
comment
@ildjarn Положительный пример обязательно будет оценен.   -  person san    schedule 17.07.2012
comment
Я тестировал clang++ 3.1 и llvm-g++ 4.2 из Xcode 4.3.3, g++ 4.2 из Xcode 4.0 и g++ 4.7 из Homebrew на паре новых компьютеров Mac и множество различных настроек (включая -funroll-all- циклы, конечно) и цикл, который ничего не делает, кроме как копирует *i в volatile. Петля так и не была раскручена. Однако это вполне может быть связано с тем, что развертывание на x86_64 является пессимизацией тела цикла, поэтому ничего не говорится о том, мог бы компилятор развернуть цикл, если бы это стоило делать.   -  person abarnert    schedule 17.07.2012
comment
@abarnert Это действительно заслуживает того, чтобы быть в одном из ответов.   -  person san    schedule 17.07.2012
comment
@abarnert: я определенно запускаю цикл с -funroll-loops (x86-64 linux, g++ 4.4.4 и g++ 4.1.2).   -  person jxh    schedule 18.07.2012
comment
@user315052 user315052 Не могли бы вы обновить свой ответ этим. Я уже голосовал, поэтому больше не могу, но это ценная информация.   -  person san    schedule 18.07.2012
comment
@jxh у вас разворачивается цикл или вы развернули этот конкретный цикл?   -  person doc    schedule 30.11.2014
comment
(примечание: вы сделали опечатку в bgin)   -  person user202729    schedule 09.05.2018


Ответы (4)


Мне кажется, что это потребует от компилятора большей сообразительности, чем обычно: сначала сделать вывод, что итератор является итератором с произвольным доступом, а затем выяснить, сколько раз выполняется цикл.

STL, полностью состоящий из шаблонов, имеет весь код inline. Итак, итераторы произвольного доступа сводятся к указателям уже тогда, когда компилятор начинает применять оптимизации. Одна из причин создания STL заключалась в том, что программисту не нужно было перехитрить компилятор. Вы должны полагаться на то, что STL сделает все правильно, пока не будет доказано обратное.

Конечно, вы все равно должны выбрать правильный инструмент из STL для использования...

Изменить: обсуждался вопрос о том, выполняет ли g++ развертывание цикла. В версиях, которые я использую, развертывание цикла не является частью -O, -O2 или -O3, и я получаю идентичную сборку для последних двух уровней со следующим кодом:

void foo (std::vector<int> &v) {
    volatile int c = 0;
    const std::vector<int>::const_iterator end = v.end();
    for (std::vector<int>::iterator i = v.begin(); i != end; ++i) {
        *i = c++;
    }
}

С соответствующей сборкой -O2 сборки:

_Z3fooRSt6vectorIiSaIiEE:
.LFB435:
        movq    8(%rdi), %rcx
        movq    (%rdi), %rax
        movl    $0, -4(%rsp)
        cmpq    %rax, %rcx
        je      .L4
        .p2align 4,,10
        .p2align 3
.L3:
        movl    -4(%rsp), %edx
        movl    %edx, (%rax)
        addq    $4, %rax
        addl    $1, %edx
        cmpq    %rax, %rcx
        movl    %edx, -4(%rsp)
        jne     .L3
.L4:
        rep
        ret

С добавлением опции -funroll-loops функция расширяется до гораздо большего размера. Но документация предупреждает об этом варианте:

Развернуть циклы, количество итераций которых можно определить во время компиляции или при входе в цикл. -funroll-loops подразумевает -frerun-cse-after-loop. Он также включает полное удаление циклов (т. е. полное удаление циклов с небольшим постоянным числом итераций). Этот параметр увеличивает размер кода и может ускорить его выполнение, а может и нет.

В качестве еще одного аргумента, чтобы отговорить вас от развертывания циклов самостоятельно, я закончу этот ответ иллюстрацией применения Duff's Device к функции foo выше:

void foo_duff (std::vector<int> &v) {
    volatile int c = 0;
    const std::vector<int>::const_iterator end = v.end();
    std::vector<int>::iterator i = v.begin();
    switch ((end - i) % 4) do {
    case 0: *i++ = c++;
    case 3: *i++ = c++;
    case 2: *i++ = c++;
    case 1: *i++ = c++;
    } while (i != end);
}

GCC имеет еще один флаг оптимизации цикла:

-ftree-loop-optimize
Оптимизация циклов для деревьев. Этот флаг включен по умолчанию в -O и выше.

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

person jxh    schedule 17.07.2012
comment
Спасибо за подробный ответ. Один вопрос: должен ли const_iterator быть итератором const? Разве первое не означает, что разыменованный объект является константой, а итератор — нет? - person san; 18.07.2012
comment
@san: я использовал const_iterator, чтобы показать, что не собираюсь использовать end для каких-либо изменений. Вы также можете сделать end const, чтобы указать, что сама переменная не изменится. Но я думаю, что компилятор может вывести это из того факта, что он локальный и не используется в цикле. - person jxh; 18.07.2012
comment
Да, я думал, что компилятор понимает это наоборот, потому что end не должен разыменовываться. Но я предполагаю, что можно было бы использовать некоторые неприятные трюки, разыменовывая за пределы контейнера, поэтому компилятор не может это запретить. - person san; 18.07.2012
comment
@san: Нет, я думаю, вы правы, компилятор может это понять, если локальный итератор не передается какой-либо другой функции по ссылке. Но я добавил еще один const специально для вас. - person jxh; 18.07.2012
comment
Ага. Если я изменю свой тестовый код для копирования из volatile в *i, а не наоборот, по крайней мере, apple-gcc-4.2 действительно развернет цикл. Странно, но… это хорошая иллюстрация того, что доказать отрицательный результат труднее (компилятор не может сделать X, потому что я не могу привести пример, подтверждающий, что X не работает). ), чем положительный (компилятор может сделать X, потому что я придумал пример, который заставляет его делать X). :) - person abarnert; 18.07.2012
comment
g++ разворачивает подсчитанные циклы. Я не думаю, что компилятор может развернуть цикл на основе итератора. - person doc; 30.11.2014
comment
@doc: В ответе упоминается, что это не делается по умолчанию, но будет сделано, если будет передана опция -funroll-loops. - person jxh; 30.11.2014
comment
@jxh Циклы на основе итератора не так просто развернуть, как циклы со счетом. Если бы v был аргументом функции, то развернуть этот цикл было бы совершенно невозможно. Для локального объекта это возможно, но потребует некоторых усилий со стороны компилятора. Не знаю, как сейчас, но некоторое время назад g++ не умел разворачивать несчетные циклы. - person doc; 30.11.2014
comment
@doc: В своем ответе я уже сказал, что пробовал, и он был развернут. - person jxh; 30.11.2014
comment
@jxh да, вы подозреваете, что он был развернут, но это не настоящее развертывание цикла. Вы видите, что gcc пытается разбить цикл на куски, но он не полностью развернут. Вот почему вы видите много дополнительного кода (также потому, что с std::vector некоторые из его внутренних компонентов могут быть фальшиво развернуты). - person doc; 30.11.2014
comment
@doc: я знаю, что он был развернут. Это было слишком долго и скучно, чтобы представить ответ. - person jxh; 30.11.2014
comment
@jxh нет, не было. Взгляните на мой ответ, где я объясняю это. - person doc; 30.11.2014
comment
Компилятор @abarnert не может развернуть эти циклы, если они не работают с локальными объектами (тем не менее, для их развертывания требуются некоторые предварительные условия). - person doc; 30.11.2014
comment
@jxh это не развертывание цикла. Это называется очисткой петли или расщеплением петли. - person doc; 30.11.2014
comment
@doc: Википедия объясняет, что означает развертывание цикла, и не не означает, что вы, кажется, думаете, что это означает. Очистка/разбиение цикла используется для уменьшения количества итераций до числа, кратного K, после чего цикл разворачивается из N итераций в N/K итераций. - person jxh; 30.11.2014
comment
@jxh и это то, что вы видите на разборке. Развертка цикла + развертывание цикла (признаюсь, что, не знаю почему, я всегда думал о развертывании цикла как о полном развертывании, хотя я знал об устройстве Даффа. Несмотря на то, что я думал иначе, чем большинство людей, моя точка зрения все же верна, потому что компилятор в большинстве случаев решит не разворачивать цикл, так как частичное развертывание [с очисткой и большим количеством кода], вероятно, слишком дорого). Но он развернет подсчитанный цикл (с фиксированным условием). - person doc; 30.11.2014
comment
@doc: это просто оптимизация цикла для небольшого количества. Компилятор примет решение провести такую ​​простую оптимизацию, когда это не составит труда. Но введите гораздо больший фиксированный счетчик, и он не коснется цикла. - person jxh; 30.11.2014
comment
@jxh эта оптимизация не что иное, как развертывание цикла. Будь то небольшой цикл или нет, простая оптимизация или нет, это развертывание цикла. - person doc; 30.11.2014
comment
@doc: Как я уже объяснял в другом комментарии, результатом оптимизации того, что вы называете циклом с подсчетом, является развернутый цикл, если счетчик невелик, оптимизация не применяется ни к одному циклу с подсчетом. Различные методы оптимизации могут привести к одинаковым результатам кода. В этом случае оптимизатор может удалить ненужную инструкцию ветвления и на самом деле не применить оптимизацию развертывания цикла. Это объясняет, почему это происходит только для небольших циклов. - person jxh; 30.11.2014
comment
@jxh это не применяется к большим циклам, потому что разворачивать эти циклы невыгодно. Конечно, это удаление ненужной инструкции ветвления, потому что каждый цикл, по сути, является инструкцией ветвления. - person doc; 01.12.2014
comment
@doc: Вы продолжаете утверждать, что это бесполезно, но пока я вас не поправил, вы не понимали, что означает оптимизация. Может показаться, что я упрям, но ясно, что вы не выполняете домашнее задание. Попробуйте вручную развернуть большую петлю и оцените, полезно это или нет. - person jxh; 01.12.2014
comment
@jxh Я признал, что допустил ошибку, когда дело дошло до терминологии. Но я вижу, что происходит при дизассемблировании, и не могу признать, что цикл не разворачивается, если он есть. - person doc; 01.12.2014
comment
@jxh это утверждение очевидно. Полное развертывание небольших циклов выгодно, потому что сгенерированный код во всех смыслах проще и меньше. С более крупными циклами это становится компромиссом, потому что более крупный код не только занимает память, но и может привести к промахам кэша. Вот почему funroll-loops отключен по умолчанию, и у вас написано, что эта опция делает код больше и может или не может ускорить его работу. С маленькими циклами дело обстоит иначе, компилятор сразу их разворачивает со стандартными флагами оптимизации. - person doc; 01.12.2014
comment
@doc: (1) Вы не знаете, будет ли это полезно или нет, потому что вы не пробовали. Я уже объяснял, что -funroll-loops нужна информация о времени, чтобы узнать оптимальную степень развертывания, но это не означает, что отсутствие развертывания является более оптимальным. (2) Я считаю, что в документации говорится, что -funroll-loops не включены по умолчанию, поэтому я утверждаю, что оптимизация, которую вы наблюдаете, не является применением оптимизации развертывания цикла, даже если результатом оптимизации является развернутый цикл . - person jxh; 02.12.2014
comment
@jxh "you do not know if it would be beneficial..." Да ладно ... Очевидно, что 4 addsd инструкции быстрее, чем 4 addsd внутри цикла = addsd инструкция + коды операций цикла, чтобы повторить ее 4 раза (переход, инициализация счетчика, увеличение и сравнение). И не я, а компилятор решает оптимизировать код таким образом. Итак, напишите команде разработчиков компилятора, что они ошибаются, делая такое предположение. - person doc; 02.12.2014
comment
@doc: Вы должны понимать, что в оптимизации кода нет ничего очевидного. Единственная разница может заключаться в большем количестве кода, а не во времени выполнения. Например, на какой-то машине инструкции могут выполняться одновременно для каждой итерации. Я хочу сказать, что вы не можете знать, что лучше, а что нет, пока не будут проведены надлежащие эксперименты по профилированию. - person jxh; 02.12.2014
comment
@jxh да, в какой-то странной архитектуре из космоса выполнение 4 дополнений одно за другим может быть медленнее, чем выполнять их внутри цикла. Но чтобы закончить это, я приложил усилия, чтобы взглянуть на внутренности gcc, и развертывание цикла реализовано как минимум в двух разных местах. funroll-loops и funroll-all-loops используют проход, определенный в loop-unroll.c. Но развертывание цикла также выполняется в проходе iv_canon, который определен здесь ="nofollow noreferrer">github.com/gcc-mirror/gcc/blob/ и включено ftree-loop-optimize на уровнях -O и выше. - person doc; 02.12.2014
comment
@jxh Выдержка из tree-ssa-loop-ivcanon.c " We also perform - complete unrolling (or peeling) when the loops is rolling few enough times". Итак, я думаю, что это закрывает эту дискуссию. - person doc; 02.12.2014
comment
@doc: Спасибо, это полезно! - person jxh; 02.12.2014
comment
@jxs TBH ftree-loop-optimize — не единственный флаг, который управляет оптимизацией цикла. Похоже, сначала нужно включить эту опцию, чтобы включить оптимизацию на деревьях, но могут потребоваться дополнительные флаги для развертывания циклов. Поэтому было бы безопаснее сказать, что -O позволяет оптимизировать простые циклы, чем "This option enables...". - person doc; 02.12.2014

Я бы предположил, что независимо от того, МОЖЕТ ли компилятор развернуть цикл с современными конвейерными архитектурами и кэшами, если только ваши «действия» не тривиальны, от этого мало пользы, и во многих случаях это будет ХИТ производительности вместо этого. благо. Если ваши действия нетривиальны, развертывание цикла создаст несколько копий этого нетривиального кода, загрузка которого в кеш займет дополнительное время, что значительно замедлит первую итерацию развернутого цикла. В то же время он удалит из кеша больше кода, который мог быть необходим для выполнения «дела», если он делает какие-либо вызовы функций, которые затем нужно будет снова перезагрузить в кеш. Цель развертывания циклов имела большой смысл до появления конвейерных архитектур без кеша без прогнозирования ветвления, цель которых состояла в том, чтобы уменьшить накладные расходы, связанные с логикой цикла. В настоящее время с аппаратным обеспечением с прогнозированием ветвлений на основе кэша ваш процессор будет хорошо конвейеризирован в следующую итерацию цикла, спекулятивно выполняя код цикла снова, к тому времени, когда вы обнаружите условие выхода i==end, после чего процессор выдаст из этого окончательного спекулятивно выполненного набора результатов. В такой архитектуре развертывание цикла имеет очень мало смысла. Это приведет к дальнейшему раздуванию кода практически без пользы.

person phonetagger    schedule 17.07.2012
comment
+1, потому что это хорошее объяснение того, почему он обычно не разворачивает цикл, даже если может. Но ОП спрашивает, если бы это когда-либо стоило делать, смог бы компилятор это сделать, и я не уверен, как полностью ответить на это, не придумывая случай, когда это стоило бы сделать… - person abarnert; 17.07.2012
comment
@abarnert Я думаю, что подражание volatile действительно считается «тривиальным» и, следовательно, отличным тестовым примером. Не могу придумать ничего короче, что не будет полностью оптимизировано - person san; 17.07.2012
comment
@san: На самом деле, похоже, что копирование из volatile - лучший тестовый пример, учитывая ответ пользователя 315052. И, может быть, поместить встроенный ассемблер внутрь цикла было бы еще более тривиально? Что ж, у нас есть проверенный ответ, так что больше никаких усилий не требуется. - person abarnert; 18.07.2012
comment
@abarnert Также могло случиться так, что решающим фактором был компилятор, потому что по количеству инструкций они должны быть совершенно эквивалентны. - person san; 18.07.2012
comment
@san: Возможно, но g++ 4.2 и 4.7 делают одно, а 4.1 и 4.4 другое, кажется немного странным. Не исключено, и, конечно, apple-llvm-g++ 4.2 может легко отличаться от стандартного 4.2… - person abarnert; 18.07.2012
comment
@abarnert на самом деле я не читал ваш комментарий о том, что вы смогли уговорить развернуться, переключив источник и пункт назначения. Так что ваш хуч прав. - person san; 18.07.2012
comment
правда, хотя развертывание цикла по-прежнему имеет смысл для простых, небольших (с точки зрения количества операций) циклов, например, когда вы добавляете один вектор (математика, а не std::) к другому. Затем компилятор может выполнить дальнейшие оптимизации, такие как векторизация, что позволит выполнить весь цикл за один шаг или значительно сократить количество шагов. - person doc; 30.11.2014

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

Итак, я говорю: не оптимизируйте преждевременно! Просто шучу :)

person David Titarenco    schedule 17.07.2012
comment
Я определяю end как постоянный итератор вне цикла. - person san; 17.07.2012
comment
Он не раскроется настолько, насколько это возможно; он развернется столько, сколько сочтет нужным развернуть. А во многих случаях и вовсе не будет. - person abarnert; 17.07.2012
comment
Я не думаю, что компилятор может развернуть цикл на основе итератора, как он может сделать с подсчитанными циклами. v.end() не является constexpr, так как компилятор может знать, где заканчивается цикл во время компиляции? Может быть, с какой-то безумной трассировкой указателей это могло бы оптимизировать локальный объект, но это совершенно невозможно, когда v будет аргументом функции. - person doc; 30.11.2014

Простой ответ: обычно НЕТ! По крайней мере, когда дело доходит до полного развертывания цикла.

Давайте проверим развертывание цикла на этой простой структуре с грязным кодом (в целях тестирования).

struct Test
{
    Test(): begin(arr), end(arr + 4) {}

    double * begin;
    double * end;
    double arr[4];
};

Сначала возьмем цикл со счетчиком и скомпилируем его без каких-либо оптимизаций.

double counted(double param, Test & d)
{
    for (int i = 0; i < 4; i++)
        param += d.arr[i];
    return param;
}

Вот что выдает gcc 4.9.

counted(double, Test&):
    pushq   %rbp
    movq    %rsp, %rbp
    movsd   %xmm0, -24(%rbp)
    movq    %rdi, -32(%rbp)
    movl    $0, -4(%rbp)
    jmp .L2
.L3:
    movq    -32(%rbp), %rax
    movl    -4(%rbp), %edx
    movslq  %edx, %rdx
    addq    $2, %rdx
    movsd   (%rax,%rdx,8), %xmm0
    movsd   -24(%rbp), %xmm1
    addsd   %xmm0, %xmm1
    movq    %xmm1, %rax
    movq    %rax, -24(%rbp)
    addl    $1, -4(%rbp)
.L2:
    cmpl    $3, -4(%rbp)
    jle .L3
    movq    -24(%rbp), %rax
    movq    %rax, -40(%rbp)
    movsd   -40(%rbp), %xmm0
    popq    %rbp
    ret

Как и ожидалось, цикл не был развёрнут, а так как оптимизаций не производилось, код в целом очень многословный. Теперь включим флаг -O3. Произведена разборка:

counted(double, Test&):
    addsd   16(%rdi), %xmm0
    addsd   24(%rdi), %xmm0
    addsd   32(%rdi), %xmm0
    addsd   40(%rdi), %xmm0
    ret

Вуаля, на этот раз цикл развернулся.


Теперь давайте рассмотрим повторяющийся цикл. Функция, содержащая цикл, будет выглядеть так.

double iterated(double param, Test & d)
{
  for (double * it = d.begin; it != d.end; ++it)
    param += *it;
  return param;
}

По-прежнему используя флаг -O3, давайте посмотрим на дизассемблирование.

iterated(double, Test&):
    movq    (%rdi), %rax
    movq    8(%rdi), %rdx
    cmpq    %rdx, %rax
    je  .L3
.L4:
    addsd   (%rax), %xmm0
    addq    $8, %rax
    cmpq    %rdx, %rax
    jne .L4
.L3:
    rep ret

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

А как насчет флагов funroll-loops и funroll-all-loops? Они будут давать результат, подобный этому

iterated(double, Test&):
    movq    (%rdi), %rsi
    movq    8(%rdi), %rcx
    cmpq    %rcx, %rsi
    je  .L3
    movq    %rcx, %rdx
    leaq    8(%rsi), %rax
    addsd   (%rsi), %xmm0
    subq    %rsi, %rdx
    subq    $8, %rdx
    shrq    $3, %rdx
    andl    $7, %edx
    cmpq    %rcx, %rax
    je  .L43
    testq   %rdx, %rdx
    je  .L4
    cmpq    $1, %rdx
    je  .L29
    cmpq    $2, %rdx
    je  .L30
    cmpq    $3, %rdx
    je  .L31
    cmpq    $4, %rdx
    je  .L32
    cmpq    $5, %rdx
    je  .L33
    cmpq    $6, %rdx
    je  .L34
    addsd   (%rax), %xmm0
    leaq    16(%rsi), %rax
.L34:
    addsd   (%rax), %xmm0
    addq    $8, %rax
.L33:
    addsd   (%rax), %xmm0
    addq    $8, %rax
.L32:
    addsd   (%rax), %xmm0
    addq    $8, %rax
.L31:
    addsd   (%rax), %xmm0
    addq    $8, %rax
.L30:
    addsd   (%rax), %xmm0
    addq    $8, %rax
.L29:
    addsd   (%rax), %xmm0
    addq    $8, %rax
    cmpq    %rcx, %rax
    je  .L44
.L4:
    addsd   (%rax), %xmm0
    addq    $64, %rax
    addsd   -56(%rax), %xmm0
    addsd   -48(%rax), %xmm0
    addsd   -40(%rax), %xmm0
    addsd   -32(%rax), %xmm0
    addsd   -24(%rax), %xmm0
    addsd   -16(%rax), %xmm0
    addsd   -8(%rax), %xmm0
    cmpq    %rcx, %rax
    jne .L4
.L3:
    rep ret
.L44:
    rep ret
.L43:
    rep ret

Сравните результаты с развернутым циклом для подсчитанного цикла. Это явно не то же самое. Здесь мы видим, что gcc разделил цикл на 8 фрагментов элементов. В некоторых случаях это может повысить производительность, поскольку условие выхода из цикла проверяется один раз за 8 обычных итераций цикла. С дополнительными флагами также может выполняться векторизация. Но это не полное развертывание цикла.

Однако повторный цикл будет развернут, если объект Test не является аргументом функции.

double iteratedLocal(double param)
{
  Test d;
  for (double * it = d.begin; it != d.end; ++it)
    param += *it;
  return param;
}

Разборка производится только с флагом -O3:

iteratedLocal(double):
    addsd   -40(%rsp), %xmm0
    addsd   -32(%rsp), %xmm0
    addsd   -24(%rsp), %xmm0
    addsd   -16(%rsp), %xmm0
    ret

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

Однако структура Test размещается статически. Сложнее обстоит дело с динамически размещаемыми структурами, такими как std::vector. Из моих наблюдений над измененной структурой Test, которая напоминает динамически размещаемый контейнер, похоже, что gcc изо всех сил пытается развернуть циклы, но в большинстве случаев сгенерированный код не так прост, как приведенный выше.


Поскольку вы просите другие компиляторы, вот вывод clang 3.4.1 (флаг -O3)

counted(double, Test&):                      # @counted(double, Test&)
    addsd   16(%rdi), %xmm0
    addsd   24(%rdi), %xmm0
    addsd   32(%rdi), %xmm0
    addsd   40(%rdi), %xmm0
    ret

iterated(double, Test&):                     # @iterated(double, Test&)
    movq    (%rdi), %rax
    movq    8(%rdi), %rcx
    cmpq    %rcx, %rax
    je  .LBB1_2
.LBB1_1:                                # %.lr.ph
    addsd   (%rax), %xmm0
    addq    $8, %rax
    cmpq    %rax, %rcx
    jne .LBB1_1
.LBB1_2:                                # %._crit_edge
    ret

iteratedLocal(double):                     # @iteratedLocal(double)
    leaq    -32(%rsp), %rax
    movq    %rax, -48(%rsp)
    leaq    (%rsp), %rax
    movq    %rax, -40(%rsp)
    xorl    %eax, %eax
    jmp .LBB2_1
.LBB2_2:                                # %._crit_edge4
    movsd   -24(%rsp,%rax), %xmm1
    addq    $8, %rax
.LBB2_1:                                # =>This Inner Loop Header: Depth=1
    movaps  %xmm0, %xmm2
    cmpq    $24, %rax
    movaps  %xmm1, %xmm0
    addsd   %xmm2, %xmm0
    jne .LBB2_2
    ret

Intel icc 13.01 (флаг -O3)

counted(double, Test&):
        addsd     16(%rdi), %xmm0                               #24.5
        addsd     24(%rdi), %xmm0                               #24.5
        addsd     32(%rdi), %xmm0                               #24.5
        addsd     40(%rdi), %xmm0                               #24.5
        ret                                                     #25.10
iterated(double, Test&):
        movq      (%rdi), %rdx                                  #30.26
        movq      8(%rdi), %rcx                                 #30.41
        cmpq      %rcx, %rdx                                    #30.41
        je        ..B3.25       # Prob 50%                      #30.41
        subq      %rdx, %rcx                                    #30.7
        movb      $0, %r8b                                      #30.7
        lea       7(%rcx), %rax                                 #30.7
        sarq      $2, %rax                                      #30.7
        shrq      $61, %rax                                     #30.7
        lea       7(%rax,%rcx), %rcx                            #30.7
        sarq      $3, %rcx                                      #30.7
        cmpq      $16, %rcx                                     #30.7
        jl        ..B3.26       # Prob 10%                      #30.7
        movq      %rdx, %rdi                                    #30.7
        andq      $15, %rdi                                     #30.7
        je        ..B3.6        # Prob 50%                      #30.7
        testq     $7, %rdi                                      #30.7
        jne       ..B3.26       # Prob 10%                      #30.7
        movl      $1, %edi                                      #30.7
..B3.6:                         # Preds ..B3.5 ..B3.3
        lea       16(%rdi), %rax                                #30.7
        cmpq      %rax, %rcx                                    #30.7
        jl        ..B3.26       # Prob 10%                      #30.7
        movq      %rcx, %rax                                    #30.7
        xorl      %esi, %esi                                    #30.7
        subq      %rdi, %rax                                    #30.7
        andq      $15, %rax                                     #30.7
        negq      %rax                                          #30.7
        addq      %rcx, %rax                                    #30.7
        testq     %rdi, %rdi                                    #30.7
        jbe       ..B3.11       # Prob 2%                       #30.7
..B3.9:                         # Preds ..B3.7 ..B3.9
        addsd     (%rdx,%rsi,8), %xmm0                          #31.9
        incq      %rsi                                          #30.7
        cmpq      %rdi, %rsi                                    #30.7
        jb        ..B3.9        # Prob 82%                      #30.7
..B3.11:                        # Preds ..B3.9 ..B3.7
        pxor      %xmm6, %xmm6                                  #28.12
        movaps    %xmm6, %xmm7                                  #28.12
        movaps    %xmm6, %xmm5                                  #28.12
        movsd     %xmm0, %xmm7                                  #28.12
        movaps    %xmm6, %xmm4                                  #28.12
        movaps    %xmm6, %xmm3                                  #28.12
        movaps    %xmm6, %xmm2                                  #28.12
        movaps    %xmm6, %xmm1                                  #28.12
        movaps    %xmm6, %xmm0                                  #28.12
..B3.12:                        # Preds ..B3.12 ..B3.11
        addpd     (%rdx,%rdi,8), %xmm7                          #31.9
        addpd     16(%rdx,%rdi,8), %xmm6                        #31.9
        addpd     32(%rdx,%rdi,8), %xmm5                        #31.9
        addpd     48(%rdx,%rdi,8), %xmm4                        #31.9
        addpd     64(%rdx,%rdi,8), %xmm3                        #31.9
        addpd     80(%rdx,%rdi,8), %xmm2                        #31.9
        addpd     96(%rdx,%rdi,8), %xmm1                        #31.9
        addpd     112(%rdx,%rdi,8), %xmm0                       #31.9
        addq      $16, %rdi                                     #30.7
        cmpq      %rax, %rdi                                    #30.7
        jb        ..B3.12       # Prob 82%                      #30.7
        addpd     %xmm6, %xmm7                                  #28.12
        addpd     %xmm4, %xmm5                                  #28.12
        addpd     %xmm2, %xmm3                                  #28.12
        addpd     %xmm0, %xmm1                                  #28.12
        addpd     %xmm5, %xmm7                                  #28.12
        addpd     %xmm1, %xmm3                                  #28.12
        addpd     %xmm3, %xmm7                                  #28.12
        movaps    %xmm7, %xmm0                                  #28.12
        unpckhpd  %xmm7, %xmm0                                  #28.12
        addsd     %xmm0, %xmm7                                  #28.12
        movaps    %xmm7, %xmm0                                  #28.12
..B3.14:                        # Preds ..B3.13 ..B3.26
        lea       1(%rax), %rsi                                 #30.7
        cmpq      %rsi, %rcx                                    #30.7
        jb        ..B3.25       # Prob 50%                      #30.7
        subq      %rax, %rcx                                    #30.7
        cmpb      $1, %r8b                                      #30.7
        jne       ..B3.17       # Prob 50%                      #30.7
..B3.16:                        # Preds ..B3.17 ..B3.15
        xorl      %r8d, %r8d                                    #30.7
        jmp       ..B3.21       # Prob 100%                     #30.7
..B3.17:                        # Preds ..B3.15
        cmpq      $2, %rcx                                      #30.7
        jl        ..B3.16       # Prob 10%                      #30.7
        movq      %rcx, %r8                                     #30.7
        xorl      %edi, %edi                                    #30.7
        pxor      %xmm1, %xmm1                                  #28.12
        lea       (%rdx,%rax,8), %rsi                           #31.19
        andq      $-2, %r8                                      #30.7
        movsd     %xmm0, %xmm1                                  #28.12
..B3.19:                        # Preds ..B3.19 ..B3.18
        addpd     (%rsi,%rdi,8), %xmm1                          #31.9
        addq      $2, %rdi                                      #30.7
        cmpq      %r8, %rdi                                     #30.7
        jb        ..B3.19       # Prob 82%                      #30.7
        movaps    %xmm1, %xmm0                                  #28.12
        unpckhpd  %xmm1, %xmm0                                  #28.12
        addsd     %xmm0, %xmm1                                  #28.12
        movaps    %xmm1, %xmm0                                  #28.12
..B3.21:                        # Preds ..B3.20 ..B3.16
        cmpq      %rcx, %r8                                     #30.7
        jae       ..B3.25       # Prob 2%                       #30.7
        lea       (%rdx,%rax,8), %rax                           #31.19
..B3.23:                        # Preds ..B3.23 ..B3.22
        addsd     (%rax,%r8,8), %xmm0                           #31.9
        incq      %r8                                           #30.7
        cmpq      %rcx, %r8                                     #30.7
        jb        ..B3.23       # Prob 82%                      #30.7
..B3.25:                        # Preds ..B3.23 ..B3.21 ..B3.14 ..B3.1
        ret                                                     #32.14
..B3.26:                        # Preds ..B3.2 ..B3.6 ..B3.4    # Infreq
        movb      $1, %r8b                                      #30.7
        xorl      %eax, %eax                                    #30.7
        jmp       ..B3.14       # Prob 100%                     #30.7
iteratedLocal(double):
        lea       -8(%rsp), %rax                                #8.13
        lea       -40(%rsp), %rdx                               #7.11
        cmpq      %rax, %rdx                                    #33.41
        je        ..B4.15       # Prob 50%                      #33.41
        movq      %rax, -48(%rsp)                               #32.12
        movq      %rdx, -56(%rsp)                               #32.12
        xorl      %eax, %eax                                    #33.7
..B4.13:                        # Preds ..B4.11 ..B4.13
        addsd     -40(%rsp,%rax,8), %xmm0                       #34.9
        incq      %rax                                          #33.7
        cmpq      $4, %rax                                      #33.7
        jb        ..B4.13       # Prob 82%                      #33.7
..B4.15:                        # Preds ..B4.13 ..B4.1
        ret                                                     #35.14

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

double countedDep(double param, Test & d)
{
    for (int i = 0; i < d.size; i++)
        param += d.arr[i];
    return param;
}

Такой шлейф и не развернется.

person doc    schedule 30.11.2014
comment
... это не развертывание цикла как таковое ... это просто ложное утверждение, если только у вас нет очень странного определения развертывания цикла. - person jxh; 30.11.2014
comment
@jxh, если это все еще цикл, то как он у вас развернулся? Он просто разбит на фрагменты из 8 элементов за счет дополнительного кода и условий. - person doc; 30.11.2014
comment
Википедия объясняет, что означает развертывание цикла, и не означает, что что вы, кажется, думаете, что это означает. - person jxh; 30.11.2014
comment
@jxh да, но это не полный цикл. Вот что я имел в виду под развертыванием цикла как такового. - person doc; 30.11.2014
comment
Я думаю, совершенно очевидно, что компилятор не может полностью развернуть цикл во что-то без циклов, если он точно не знает, сколько итераций будет выполнено. Для меня также очевидно, что полное развертывание цикла является бессмысленной оптимизацией, если только количество итераций не очень мало, поэтому я не уверен, что ваша узкая точка является каким-либо улучшением по сравнению с моим правильным ответом. - person jxh; 30.11.2014
comment
@jxh, тогда почему вы написали, что развертывание цикла не является частью O, O2, O3, а оно есть? Компилятор предпочитает не развертывать итерированные циклы, потому что это бессмысленно, в то время как он предпочитает разворачивать подсчитанные циклы, поэтому ваш ответ в лучшем случае уклончив. Как правило, он не разворачивает цикл, выраженный итераторами. - person doc; 30.11.2014
comment
Я указал правильный факт о том, как работает GCC. В документации указано, какие параметры включены для каждого уровня оптимизации, и -funroll-loops не является частью ни одного из них. То, что вы показываете, не является развертыванием цикла, так как большинство людей понимают, что означает этот термин (в то время как полное развертывание цикла — это термин, который вы придумали для этого поста). - person jxh; 30.11.2014
comment
@jxh, если в моих примерах, скомпилированных с флагом O3, это не развертывание цикла, то что это, по вашему мнению? - person doc; 30.11.2014
comment
Это просто оптимизация для повторения четырех операторов. Измените размер массива с 4 на 1024, повторите 1024 раза и посмотрите, что получится. Вы увидите, что ваша теория неверна. - person jxh; 30.11.2014
comment
@jxh, очевидно, эта оптимизация называется разверткой цикла. Конечно, компилятор может отказаться от развертывания цикла, если он увидит, что размер сгенерированного кода (например, для 1024 отсчетов) не выгоден. Измените его на 16, и он разворачивается. Ха-ха, моя теория неверна, потому что она не разворачивала цикл на 1024 счета? РЖУ НЕ МОГУ. Почему число 1024 что-то подтверждает (или опровергает)? - person doc; 30.11.2014
comment
Если оптимизация действительно представляет собой развертывание цикла, то количество циклов не имеет значения. Вместо этого он принимает выборочное эвристическое решение, когда преобразовать небольшой цикл в эквивалентные операторы. Результатом является развернутый цикл, но он не применяет оптимизацию развертывания цикла к циклу в общем смысле. - person jxh; 30.11.2014
comment
@jxh технически это развертывание цикла. Невероятно, как вы можете отрицать этот факт. Он не выполняет развертывание цикла для большего количества, потому что, как указано в документации, развертывание цикла может ускорить или замедлить выполнение кода, поэтому не стоит выполнять эту оптимизацию для каждого отдельного цикла за счет большего двоичного кода. Но это полезно для небольших циклов. - person doc; 30.11.2014
comment
Если компилятор знает только, как выполнить развертывание цикла для подсчитанных циклов (что утверждает ваш пост), не имеет значения, каков счет. Теперь вы признаете, что это так. Тем не менее, оптимизация развертывания цикла не заботится о количестве. Причина, по которой компилятор не включает развертывание цикла по умолчанию, заключается в том, что ему действительно необходимо профилировать код, чтобы определить, действительно ли общая оптимизация развертывания цикла будет быстрее. Есть способы вернуть компилятору информацию о профилировании, чтобы он лучше принимал решения по оптимизации. - person jxh; 30.11.2014
comment
В любом случае, я слишком устал, чтобы объяснять это дальше. Вам просто нужно верить в то, что вы хотите. - person jxh; 30.11.2014
comment
@jxh, откуда вы взяли утверждение, что счет не имеет значения? Кто сказал, что решение разворачивать цикл или нет не может зависеть от количества циклов? И когда это зависит от количества циклов, это означает, что цикл не разворачивается? Это нелепо. Я сравниваю счетный цикл с повторным циклом для того же количества итераций. На самом деле gcc будет выполнять развертывание цикла и для повторного цикла, если объект является локальным. - person doc; 30.11.2014