Variabel Lokal vs. Optimasi Penyusun Variabel Kelas; Berhasil vs. Tidak Berfungsi

Saya memiliki contoh kode di mana optimasi langsung tidak berfungsi ketika disusun sebagai variabel kelas, namun berfungsi sebagai variabel lokal; Saya ingin tahu: mengapa optimasi tidak terjadi pada formulasi variabel kelas?

Maksud dari kode contoh saya adalah untuk memiliki kelas yang diaktifkan atau dinonaktifkan saat konstruksi dan mungkin diubah selama masa pakainya. Saya berharap, ketika objek dinonaktifkan seumur hidupnya, kompiler akan mengoptimalkan semua kode yang dijalankan secara kondisional ketika objek diaktifkan.

Secara khusus, saya memiliki std::ofstream yang hanya ingin saya tulis ketika "diaktifkan". Saat dinonaktifkan, saya ingin semua output yang diformat dilewati. (Kelas saya yang sebenarnya melakukan pemformatan pesannya sendiri yang tidak sepele.)

Saya menemukan bahwa ketika saya merumuskan ini sebagai sebuah kelas, saya tidak mendapatkan optimasi yang saya harapkan. Namun, jika saya mereplikasi semua kode sebagai variabel lokal, saya melihat perilaku yang diharapkan.

Selain itu, saya menemukan bahwa jika saya tidak melakukan panggilan std::ofstream seperti 'buka', 'pengecualian', atau 'hapus' di mana pun di badan metode kelas contoh, saya juga mendapatkan pengoptimalan yang diharapkan. (Namun, desain saya mengharuskan melakukan panggilan seperti itu di std::ofstream, jadi bagi saya ini adalah poin yang bisa diperdebatkan.) Kode di bawah ini menggunakan MACRO DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR untuk mengizinkan seseorang mencoba kasus ini.

Kode contoh saya menggunakan ekspresi 'asm' untuk memasukkan komentar ke dalam kode Majelis yang dihasilkan. Jika seseorang memeriksa output kompiler di Majelis, saya berharap tidak ada Majelis di antara komentar 'tes yang dinonaktifkan'. Saya mengamati pertemuan antara komentar 'tes nonaktif kelas', namun tidak ada pertemuan antara komentar 'tes nonaktif lokal'.

Masukkan kode 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;
}

Kode perakitan keluaran:

##### 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. #####

Saya menyadari bahwa ini mungkin terkait dengan kompiler yang saya gunakan, yaitu: g++-4.6 (Debian 4.6.1-4) 4.6.1; bendera kompiler: -Wall -S -O2. Namun, ini sepertinya optimasi yang sederhana. Saya merasa sulit untuk percaya bahwa kompilernya mungkin yang kacau.

Bantuan, wawasan, atau bimbingan apa pun sangat dihargai.


person Charles L Wilcox    schedule 17.07.2011    source sumber
comment
Bisakah Anda memberikan opsi pengoptimalan yang Anda gunakan? Saya tidak dapat mereproduksi temuan Anda pada pengujian yang dinonaktifkan oleh penduduk setempat, yaitu beberapa kode sedang dihasilkan.   -  person Luc Danton    schedule 17.07.2011
comment
Jika Anda belum melakukannya, saya sarankan Anda menyampaikan hal ini di milis gcc-help.   -  person Luc Danton    schedule 17.07.2011
comment
Luc, saya menambahkan flag compiler ke postingan saya. Tidak ada yang mewah, hanya -Dinding -S -O2.   -  person Charles L Wilcox    schedule 17.07.2011
comment
Luc, menurut saya ini belum merupakan hal khusus GCC. Menambahkan tanda -Winline menunjukkan bahwa GCC tidak menyejajarkan konstruktor dengan kasus #define DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR 0. Ironisnya, hal ini membuat sejalan dengan konstruktor dengan #define DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR 1.   -  person Charles L Wilcox    schedule 17.07.2011


Jawaban (2)


Cukup mudah. Saat Anda menulis kode secara langsung sebagai variabel lokal, maka kode tersebut akan dimasukkan dan kompiler melakukan pelipatan konstan. Saat Anda berada dalam cakupan kelas, maka kode tersebut tidak disisipkan dan nilai m_b_enabled tidak diketahui, sehingga kompiler harus melakukan panggilan. Untuk membuktikan bahwa kode tersebut sama secara semantik dan melakukan optimasi ini, tidak hanya panggilan itu yang harus dimasukkan, tetapi setiap akses ke kelas. Kompiler mungkin memutuskan bahwa memasukkan kelas tidak akan menghasilkan manfaat yang cukup. Kompiler juga dapat memilih untuk tidak memasukkan kode karena mereka tidak tahu caranya, dan ekspresi asm sebaris adalah hal yang dapat menyebabkan mereka melakukannya, karena kompiler tidak tahu cara menangani kode Majelis Anda.

Biasanya, Anda akan menempatkan breakpoint dan memeriksa pembongkaran. Lagipula itulah yang akan saya lakukan di Visual Studio. Assembler inline dalam bentuk apa pun dapat sangat merusak pengoptimal.

Ketika saya menghapus ekspresi assembler, maka Visual Studio memasukkan kodenya- dan segera tidak melakukan optimasi. Masalah dengan menumpuk lintasan pengoptimalan adalah Anda tidak akan pernah mendapatkan urutan yang tepat untuk menemukan semua potensi pengoptimalan.

person Puppy    schedule 17.07.2011
comment
FWIW, menggunakan snapshot GCC 4.7 dan memeriksa kode yang dibongkar tanpa perakitan di sumbernya memiliki hasil yang sama seperti yang Anda lakukan: versi kelas masih bercabang. - person Luc Danton; 17.07.2011
comment
@DeadMG, Anda benar tentang inlining, saya baru saja menemukan flag -Winline GCC, dan dikatakan bahwa kedua panggilan ke konstruktor tidak inline. :-( Saya kira intuisi saya tentang apa yang seharusnya menjadi inlin-able sangat salah. Sejauh ekspresi 'asm' mempengaruhi pengoptimal... Saya tidak berpengalaman di sini. Namun saya akan berasumsi bahwa karena perakitan yang ditambahkan tidak mengacu pada variabel apa pun, maka dampaknya akan kecil atau tidak sama sekali. - person Charles L Wilcox; 17.07.2011

Seperti yang Anda katakan, ini tergantung pada kompiler. Tapi tebakan saya:

Pengoptimal dapat membuktikan bahwa tidak ada kode lain yang dapat mengubah objek bool b_enabled, karena objek tersebut bersifat lokal dan Anda tidak pernah mengambil alamatnya atau mengikat referensi ke objek tersebut. Versi lokal mudah dioptimalkan.

Jika DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR benar, konstruktor Test_Ofstream:

  • Memanggil konstruktor ofstream(const char*)
  • Inisialisasi anggota m_b_enabled

Karena tidak ada operasi antara menginisialisasi test_ofstream.m_b_enabled dan mengujinya, pengoptimalan ini hanya sedikit lebih rumit, namun sepertinya g++ masih berhasil.

Jika DISABLE_OPEN_OFSTREAM_AFTER_CONSTRUCTOR salah, konstruktor Test_Ofstream:

  • Memanggil konstruktor default ofstream
  • Inisialisasi anggota m_b_enabled
  • Panggilan m_ofstream.open(const char*)

Pengoptimal tidak diperbolehkan berasumsi bahwa ofstream::open tidak akan mengubah test_ofstream.m_b_enabled. Kita tahu seharusnya tidak demikian, namun secara teori fungsi perpustakaan non-inline dapat mengetahui objek lengkap test_ofstream yang berisi argumen 'ini', dan memodifikasinya seperti itu.

person aschepler    schedule 17.07.2011
comment
aschepler, saya telah menemukan bukti bahwa GCC tidak menyelaraskan konstruktor seperti yang saya harapkan secara naif. Jadi kompiler bahkan tidak akan sampai pada titik yang Anda spekulasikan. Saya tidak yakin apakah mekanisme yang Anda jelajahi di sini mungkin dilakukan. - person Charles L Wilcox; 17.07.2011