Оптимизация компилятора локальных переменных и переменных класса; Работает или не работает

У меня есть пример кода, где прямая оптимизация не работает, когда структурирована как переменные класса, но работает как локальные переменные; Я хочу знать: почему не происходит оптимизация формулировки переменных класса?

Цель моего примера кода состоит в том, чтобы иметь класс, который либо включается, либо отключается при создании и, возможно, изменяется в течение его жизни. Я ожидаю, что когда объект отключен на все время его жизни, компилятор оптимизирует весь код, который условно выполняется, когда объект включен.

В частности, у меня есть std::ofstream, в который я хочу писать только тогда, когда он «включен». Когда отключено, я хочу, чтобы весь форматированный вывод был пропущен. (Мой реальный класс выполняет собственное нетривиальное форматирование сообщений.)

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

Кроме того, я обнаружил, что если я не делаю вызовы std::ofstream, такие как «open», «exceptions» или «clear» в теле методов класса-примера, я также получаю ожидаемые оптимизации. (Однако мой дизайн требует выполнения таких вызовов std::ofstream, поэтому для меня это спорный вопрос.) В приведенном ниже коде используется MACRO DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR, позволяющий попробовать этот случай.

В моем примере кода используются выражения asm для вставки комментариев в сгенерированный ассемблерный код. Если кто-то проверит вывод компилятора в сборке, я ожидаю, что между комментариями «disabled-test» сборки не будет. Я наблюдаю сборку между комментариями «class disabled-test», но не сборку между комментариями «locals disabled-test».

Входной код С++:

#include <fstream> // ofstream

#define DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR 0

class Test_Ofstream
{
public:
    Test_Ofstream( const char a_filename[],
                   bool a_b_enabled )
    #if DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR
        : m_ofstream( a_filename ),
          m_b_enabled( a_b_enabled )
    {
    }
    #else
        : m_ofstream(),
          m_b_enabled( a_b_enabled )
    {
        m_ofstream.open( a_filename );
    }
    #endif

    void write_test()
    {
        if( m_b_enabled )
        {
            m_ofstream << "Some text.\n";
        }
    }

private:
    std::ofstream m_ofstream;
    bool m_b_enabled;
};

int main( int argc, char* argv[] )
{
    {
        Test_Ofstream test_ofstream( "test.txt", true );
        asm( "# BEGIN class enabled-test" );
        test_ofstream.write_test();
        asm( "# END class enabled-test" );
    }

    {
        Test_Ofstream test_ofstream( "test.txt", false );
        asm( "# BEGIN class disabled-test" );
        test_ofstream.write_test();
        asm( "# END class disabled-test" );
    }

    {
        bool b_enabled = true;
        #if DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR
        std::ofstream test_ofstream( "test.txt" );
        #else
        std::ofstream test_ofstream;
        test_ofstream.open( "test.txt" );
        #endif
        asm( "# BEGIN locals enabled-test" );
        if( b_enabled )
        {
            test_ofstream << "Some text.\n";
        }
        asm( "# END locals enabled-test" );
    }

    {
        bool b_enabled = false;
        #if DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR
        std::ofstream test_ofstream( "test.txt" );
        #else
        std::ofstream test_ofstream;
        test_ofstream.open( "test.txt" );
        #endif
        asm( "# BEGIN locals disabled-test" );
        if( b_enabled )
        {
            test_ofstream << "Some text.\n";
        }
        asm( "# END locals disabled-test" );
    }

    return 0;
}

Выходной ассемблерный код:

##### Cut here. #####
#APP
# 53 "test_ofstream_optimization.cpp" 1
        # BEGIN class disabled-test
# 0 "" 2
#NO_APP
        cmpb        $0, 596(%esp)
        je  .L22
        movl        $.LC1, 4(%esp)
        movl        %ebx, (%esp)
.LEHB9:
        call        _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
.LEHE9:
.L22:
#APP
# 55 "test_ofstream_optimization.cpp" 1
        # END class disabled-test
# 0 "" 2
#NO_APP
##### Cut here. #####
#APP
# 116 "test_ofstream_optimization.cpp" 1
        # BEGIN locals disabled-test
# 0 "" 2
# 121 "test_ofstream_optimization.cpp" 1
        # END locals disabled-test
# 0 "" 2
#NO_APP
##### Cut here. #####

Я понимаю, что это, возможно, связано с компилятором, который я использую, а именно: g++-4.6 (Debian 4.6.1-4) 4.6.1; флаги компилятора: -Wall -S -O2. Однако это кажется такой простой оптимизацией, что мне трудно поверить, что это может быть ошибка компилятора.

Любая помощь, понимание или руководство приветствуется.


person Charles L Wilcox    schedule 17.07.2011    source источник
comment
Можете ли вы предоставить параметры оптимизации, которые вы используете? Я не могу воспроизвести ваши выводы по тесту с отключенными местными жителями, т. е. генерируется некоторый код .   -  person Luc Danton    schedule 17.07.2011
comment
Если вы еще этого не сделали, я рекомендую вам внести это в список рассылки gcc-help.   -  person Luc Danton    schedule 17.07.2011
comment
Люк, я добавил флаги компилятора в свой пост. Ничего необычного, просто -Стена -С -О2.   -  person Charles L Wilcox    schedule 17.07.2011
comment
Люк, я пока не думаю, что это специфично для GCC. Добавление флага -Winline показывает, что GCC не встраивает конструктор в случае #define DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR 0. По иронии судьбы, он встраивает конструктор с помощью #define DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR 1.   -  person Charles L Wilcox    schedule 17.07.2011


Ответы (2)


Довольно просто. Когда вы пишете код непосредственно как локальную переменную, код встраивается, а компилятор выполняет свертывание констант. Когда вы находитесь в области класса, код не встроен, а значение m_b_enabled неизвестно, поэтому компилятор должен выполнить вызов. Чтобы доказать, что код семантически идентичен, и выполнить эту оптимизацию, не только этот вызов должен быть встроен, но и каждый доступ к классу. Компилятор вполне может решить, что встраивание класса не принесет достаточной пользы. Компиляторы также могут отказаться от встроенного кода, потому что они не знают, как это сделать, а встроенные выражения asm — это именно то, что может заставить их это сделать, поскольку компилятор не знает, как обрабатывать ваш ассемблерный код.

Обычно вы ставите точку останова и проверяете дизассемблирование. Во всяком случае, это то, что я сделал бы в Visual Studio. Любой встроенный ассемблер может нанести вред оптимизатору.

Когда я удалил ассемблерные выражения, Visual Studio встроила код и сразу же не выполнила оптимизацию. Проблема с наложением проходов оптимизации заключается в том, что вы никогда не сможете получить правильный порядок, чтобы найти все потенциальные оптимизации.

person Puppy    schedule 17.07.2011
comment
FWIW, использование моментального снимка GCC 4.7 и проверка дизассемблированного кода без сборки в исходном коде дает те же результаты, что и вы: версия класса все еще разветвляется. - person Luc Danton; 17.07.2011
comment
@DeadMG, вы правы насчет встраивания, я только что обнаружил флаг GCC -Winline, и он говорит, что оба вызова конструктора не встроены. :-( Я предполагаю, что моя интуиция относительно того, что должно быть встраиваемым, очень ошибочна. Что касается выражений 'asm', влияющих на оптимизатор... Я неопытен здесь. Однако я бы предположил, что поскольку добавленная сборка не ссылается на какие-либо переменные, она не должна иметь практически никакого влияния. - person Charles L Wilcox; 17.07.2011

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

Оптимизатор может доказать, что никакой другой код никогда не сможет изменить объект bool b_enabled, поскольку он локальный, и вы никогда не берете его адрес и не привязываете к нему ссылку. Локальная версия легко оптимизируется.

Когда DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR истинно, конструктор Test_Ofstream:

  • Вызывает конструктор ofstream(const char*)
  • Инициализирует член m_b_enabled

Поскольку между инициализацией test_ofstream.m_b_enabled и его тестированием нет операций, эта оптимизация лишь немного сложнее, но похоже, что g++ все еще справляется с этим.

Когда DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR имеет значение false, конструктор Test_Ofstream:

  • Вызывает конструктор по умолчанию ofstream
  • Инициализирует элемент m_b_enabled
  • Звонки m_ofstream.open(const char*)

Оптимизатор не может предполагать, что ofstream::open не изменит test_ofstream.m_b_enabled. Мы знаем, что этого не должно быть, но теоретически эта не встроенная библиотечная функция может вычислить полный объект test_ofstream, который содержит аргумент this, и модифицировать его таким образом.

person aschepler    schedule 17.07.2011
comment
aschepler, я нашел доказательства того, что GCC не встраивает конструкторы, как я наивно ожидал. Таким образом, компилятор даже не дойдет до того момента, о котором вы размышляете. Я не уверен, что механизм, который вы здесь изучаете, возможен. - person Charles L Wilcox; 17.07.2011