Простой l oop оптимизирован под clang, как? - PullRequest
1 голос
/ 29 мая 2020

Из CppCon 2014: Майк Эктон «Data-Oriented Design and C ++» , он показывает этот простой l oop

int Foo::Bar(int count)
{
    int value = 0;
    for (int i=0;i<count;i++)
    {
        if ( m_NeedParentUpdate )
        {
            value++;
        }
    }
    return (value);
}

Оптимизируется с помощью clang как такового

enter image description here

Я не понимаю, что здесь происходит. Почему этот код плохой, и почему он оптимизирован как таковой с помощью clang, почему это работает?

О l oop он также говорит: «Я уверен, что вы можете оптимизировать это в своей голове» . Я не понимаю. Как мне это оптимизировать?

Ответы [ 2 ]

1 голос
/ 29 мая 2020

Причина, по которой это считается плохим, заключается в том, что вам придется читать переменную-член m_NeedParentUpdate на каждой итерации. Если бы это было большое for l oop, и вы пытаетесь прочитать кучу других переменных-членов, тогда чтение этого m_NeedParentUpdate может занять место в вашем кеше, потенциально уменьшая эффективное использование кеша. Это приведет к снижению производительности.

Специфика снижения производительности будет зависеть от структуры данных класса, выполняющего этот код, и от того, какие другие переменные-члены доступны внутри for l oop.

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

int Foo::Bar(int count)
{
    int value = 0;
    if ( m_NeedParentUpdate )
    {
        for (int i=0;i<count;i++)
        {

            value++;
        }
    }
    return (value);
}
1 голос
/ 29 мая 2020

l oop увеличивает value count раз, если m_NeedParentUpdate не false.

Из сгенерированного кода кажется, что m_NeedParentUpdate является логическим значением, сохраненным как беззнаковое байт по смещению 0 от this. Оптимизатор, вероятно, обнаружит, что m_NeedParentUpdate является константой в l oop, поэтому тест можно переместить за пределы l oop. Программист уже должен был написать код таким образом, и это может быть то, что Майк Эктон называет Я уверен, что вы можете оптимизировать это в своей голове .

Вот переписанная версия:

class Foo {
    bool m_NeedParentUpdate;
    int Bar(int count);
};

int Foo::Bar(int count) {
    int value = 0;
    if (m_NeedParentUpdate) {
        for (int i = 0; i < count; i++) {
            value++;
        }
    }
    return value;
}

Обратите внимание, однако, что дальнейшая оптимизация кода в вашей голове может привести к уменьшению l oop до value += count;, но это будет неверно для отрицательных значений count , что не так очевидно на первый взгляд.

Оптимизатор может обнаружить шаблон l oop и выполнить оптимизацию как:

int Foo::Bar(int count) {
    int value = 0;
    if (m_NeedParentUpdate) {
        if (count >= 0) {
            value += count;
        }
    }
    return value;
}

Или, что эквивалентно:

int Foo::Bar(int count) {
    int value = 0;
    if (count >= 0) {
        if (m_NeedParentUpdate) {
            value = count;
        }
    }
    return value;
}

Преобразование m_ParentNeedUpdate в unsigned и его отрицание дает 0 для false и все биты один для true. Маскирование count с этим значением даст 0 или count.

int Foo::Bar(int count) {
    int value = 0;
    if (count >= 0) {
        value = -(unsigned)m_NeedParentUpdate & count;
    }
    return value;
}

Обратите внимание, однако, что в коде все еще есть тест и инструкция перехода. Его можно дополнительно оптимизировать как:

int Foo::Bar(int count) {
    // equivalent code, but definitely not readable
    return -(unsigned)m_NeedParentUpdate & -(unsigned)(count >= 0) & count;
}

При компиляции как gcc, так и clang получается код без веток, как это можно увидеть в GodBolt's Compiler Explorer . Но ни один из компиляторов не сокращает исходный код до этого единственного лайнера.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...