Какую технику выхода из цикла «посетитель» легче проанализировать компилятору? - PullRequest
0 голосов
/ 09 июля 2011

В настоящее время я генерирую код, предназначенный для компилятора C ++.Я создаю класс, который будет принимать объект посетителя, вызывать метод accept для него с несколькими объектами.Тем не менее, я хочу, чтобы у посетителя была возможность «прервать», то есть у посетителя должен быть способ указать, что он хочет прекратить остальные вызовы на прием, потому что его интерес потерян.

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

Первый способзаключается в создании метода doVisiting, который ожидает метод Accept для посетителя, указывающий, должен ли он продолжать:

template<class VisitorT>
void doVisiting(VisitorT& visitor)
{
    if(visitor.accept(object1)) {
        if(visitor.accept(object2)) {
            if(visitor.accept(object3)) {
                visitor.accept(object4);
            }
        }
    }
}

Одним из преимуществ этого способа является то, что если любой из методов accept жестко задан для возврата false или true, яожидать, что постоянное распространение компилятора сработает, обрезает проверки if и принимает вызовы.Я думаю, что это разумное предположение, потому что постоянное распространение - это то, что должен реализовать любой оптимизирующий компилятор.

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

struct myvisitor
{
    bool stop;
    void accept(object_type1& o)
    {
        if(stop)
            return;

        // do work
    }

    void accept(object_type2& o)
    {
        if(stop)
            return;

        // do work

        if(some_break_worthy_condition)
            // if we were returning bool instead of void,
            // we would have a return false here.
            stop = true;
    }

    // .. other accept methods
};

В первом методе if находятся вне вызовов accept, а во втором - внутри них if.,Второй также обязательно включает в себя сохранение состояния.Моя интуиция заключается в том, что последний требует более сложного анализа, но, возможно, я недооцениваю компиляторы.

Само собой разумеется, что меня интересуют другие предложения, которые компиляторы будут обрабатывать еще лучше;)

1 Ответ

2 голосов
/ 09 июля 2011

При первом варианте ваш неоптимизированный упрощенный код будет выглядеть примерно так:

call visitor.accept o1
branch on result is false to end
call visitor.accept o2
branch on result is false to end
call visitor.accept o3
branch on result is false to end
call visitor.accept o4

Так как же компилятор может оптимизировать это?Ну, теперь у меня есть идея.Кажется, уже достаточно эффективно.Так что вам нужно копать глубже.

Насколько сложна будет часть "call vistor.accept".Скажем, что посетитель не указатель, тогда целевой адрес инструкции вызова известен компилятору во время компиляции.И это можно сделать простой инструкцией вызова.Просто нужно будет поместить аргумент oX где-нибудь, где вызываемая функция сможет его найти.Компилятор МОЖЕТ дополнительно оптимизировать это, фактически поместив код из встроенной структуры myvisitor в приведенный выше код.Чем бы не требовался оператор вызова вообще, но это увеличило бы размер файла, увеличив риск того, что ваш код больше не помещается в кеш вашего процессора и т. Д., Поэтому, безусловно, есть некоторые дискуссии о том, подходит ли это длякомпилятор, тем более что вызовы на современных процессорах очень быстрые.

Как выглядит ваш другой неоптимизированный, но упрощенный код?

call visitor.accept o1
call visitor.accept o2
...

при каждом обращении к посетителю дополнительно происходит следующее:

branch on stop is true to end
[Some do-Stuff-code]

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

Так что же можеткомпилятор для тебя во втором примере?Вы можете ожидать, что компилятор выяснит, что если переменная stop установлена ​​в true, он может перепрыгнуть через все оставшиеся операторы вызова.Но для этого нужен сложный компилятор.Даже если компилятор может понять это, он все равно может ничего не делать, потому что в многопоточной среде вы не знаете, будет ли поток инструкций обязательно свободен от побочных эффектов.

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

Если вас больше интересует, что компиляторы могут сделать для вас, вас может заинтересовать беседа Знай своего компилятора Феликса фон Лейтнера2007 .

...