Локальные переменные против переменных класса Оптимизация компилятора; Работает против не работает - PullRequest
0 голосов
/ 17 июля 2011

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

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

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

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

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

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

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

#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.Тем не менее, это кажется такой простой оптимизацией, что мне трудно поверить, что это может привести к поломке компилятора.

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

Ответы [ 2 ]

5 голосов
/ 17 июля 2011

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

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

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

0 голосов
/ 17 июля 2011

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

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

Когда DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR имеет значение true, конструктор 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, и изменить его таким образом.

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