Подтвердить что ты не робот

Оптимизация выписки из цикла

Перемещение переменной-члена в локальную переменную уменьшает количество записей в этом цикле, несмотря на наличие ключевого слова __restrict. Это использует GCC-O3. Clang и MSVC оптимизируют записи в обоих случаях. [Обратите внимание, что поскольку этот вопрос был отправлен, мы заметили, что добавление __restrict к вызывающей функции заставило GCC также переместить хранилище из цикла. См. Ссылку godbolt ниже и комментарии]

class X
{
public:
    void process(float * __restrict d, int size)
    {
        for (int i = 0; i < size; ++i)
        {
            d[i] = v * c + d[i];
            v = d[i];
        }
    }

    void processFaster(float * __restrict d, int size)
    {
        float lv = v;
        for (int i = 0; i < size; ++i)
        {
            d[i] = lv * c + d[i];
            lv = d[i];
        }
        v = lv;
    }

    float c{0.0f};
    float v{0.0f};
};

С gcc-O3 первый имеет внутренний цикл, который выглядит так:

.L3:
  mulss xmm0, xmm1
  add rdi, 4
  addss xmm0, DWORD PTR [rdi-4]
  movss DWORD PTR [rdi-4], xmm0
  cmp rax, rdi
  movss DWORD PTR x[rip+4], xmm0        ;<<< the extra store
  jne .L3
.L1:
  rep ret

Второй здесь:

.L8:
  mulss xmm0, xmm1
  add rdi, 4
  addss xmm0, DWORD PTR [rdi-4]
  movss DWORD PTR [rdi-4], xmm0
  cmp rdi, rax
  jne .L8
.L7:
  movss DWORD PTR x[rip+4], xmm0
  ret

Для полного кода см. https://godbolt.org/g/a9nCP2.

Почему компилятор не выполняет здесь lv-оптимизацию?

Я предполагаю, что 3 обращения к памяти за один цикл хуже, чем 2 (при условии, что размер не является небольшим числом), хотя я еще не измерил это.

Я прав, чтобы сделать это предположение?

Я думаю, что наблюдаемое поведение должно быть одинаковым в обоих случаях.

4b9b3361

Ответ 1

Это, по-видимому, вызвано отсутствующим квалификатором __restrict в функции f_original. __restrict - расширение GCC; не совсем ясно, как ожидается, что он будет вести себя на С++. Возможно, это ошибка компилятора (пропущенная оптимизация), которая, по-видимому, исчезает после вложения.

Ответ 2

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

Ответ 3

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

Во втором случае внешний видимый эффект v отсутствует, поэтому его не нужно хранить.

В первом случае есть потенциал, который может видеть какой-то внешний элемент, компилятор в это время не знает, что не будет потоков, которые могли бы его изменить, но он знает, что ему не нужно его читать поскольку он не является ни атомным, ни изменчивым. И изменение d[] другой видимой извне переменной делает необходимым сохранение.

Если рассуждения составителей компилятора, ну, ни d, ни v нестабильны или не атомарны, поэтому мы можем просто сделать все это с использованием "as-if", тогда компилятор должен быть уверен, что никто не может коснуться v вообще. Я уверен, что это произойдет в одной из новых версий, так как синхронизация до возвращения не произойдет, и это будет в случае 99 +% всех случаев. Затем программистам придется поменять либо изменчивые, либо атомарные переменные, которые, как мне кажется, я мог бы жить.