Cara kami menterjemah 10 juta baris kod C++ kepada standard C++14 (dan kemudian kepada C++17)

Beberapa ketika dahulu (pada musim luruh 2016), semasa pembangunan versi seterusnya platform teknologi 1C:Enterprise, persoalan timbul dalam pasukan pembangunan tentang menyokong standard baharu C ++ 14 dalam kod kami. Peralihan kepada standard baharu, seperti yang kami anggap, akan membolehkan kami menulis banyak perkara dengan lebih elegan, ringkas dan boleh dipercayai, serta akan memudahkan sokongan dan penyelenggaraan kod tersebut. Dan nampaknya tiada yang luar biasa dalam terjemahan, jika bukan kerana skala asas kod dan ciri khusus kod kami.

Bagi mereka yang tidak tahu, 1C:Enterprise ialah persekitaran untuk pembangunan pesat aplikasi perniagaan merentas platform dan masa jalan untuk pelaksanaannya pada OS dan DBMS yang berbeza. Secara umum, produk mengandungi:

  • Kluster Pelayan Aplikasi, berjalan pada Windows dan Linux
  • Pelanggan, bekerja dengan pelayan melalui http(s) atau protokol binarinya sendiri, berfungsi pada Windows, Linux, macOS
  • Pelanggan web, berjalan dalam pelayar Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari (ditulis dalam JavaScript)
  • Persekitaran pembangunan (Konfigurator), berfungsi pada Windows, Linux, macOS
  • Alat Pentadbiran pelayan aplikasi, dijalankan pada Windows, Linux, macOS
  • Pelanggan mudah alih, menyambung ke pelayan melalui http(s), berfungsi pada peranti mudah alih yang menjalankan Android, iOS, Windows
  • Platform mudah alih β€” rangka kerja untuk mencipta aplikasi mudah alih luar talian dengan keupayaan untuk menyegerak, berjalan pada Android, iOS, Windows
  • Persekitaran pembangunan 1C:Alat Pembangunan Perusahaan, ditulis dalam bahasa Jawa
  • pelayan Sistem Interaksi

Kami cuba menulis kod yang sama untuk sistem pengendalian yang berbeza sebanyak mungkin - pangkalan kod pelayan adalah 99% biasa, pangkalan kod klien adalah kira-kira 95%. Platform teknologi 1C:Enterprise ditulis terutamanya dalam C++ dan ciri kod anggaran diberikan di bawah:

  • 10 juta baris kod C++,
  • 14 ribu fail,
  • 60 ribu kelas,
  • setengah juta kaedah.

Dan semua perkara ini perlu diterjemahkan ke dalam C++14. Hari ini kami akan memberitahu anda bagaimana kami melakukan ini dan perkara yang kami temui dalam proses tersebut.

Cara kami menterjemah 10 juta baris kod C++ kepada standard C++14 (dan kemudian kepada C++17)

Penafian

Semua yang ditulis di bawah tentang kerja lambat/cepat, (bukan) penggunaan memori yang besar oleh pelaksanaan kelas standard dalam pelbagai perpustakaan bermakna satu perkara: ini benar UNTUK KAMI. Ada kemungkinan bahawa pelaksanaan standard akan paling sesuai untuk tugas anda. Kami bermula dari tugas kami sendiri: kami mengambil data yang biasa untuk pelanggan kami, menjalankan senario biasa pada mereka, melihat prestasi, jumlah memori yang digunakan, dsb., dan menganalisis sama ada kami dan pelanggan kami berpuas hati dengan keputusan tersebut atau tidak . Dan mereka bertindak bergantung pada.

Apa yang kita ada

Pada mulanya, kami menulis kod untuk platform 1C:Enterprise 8 dalam Microsoft Visual Studio. Projek ini bermula pada awal 2000-an dan kami mempunyai versi Windows sahaja. Sememangnya, sejak itu kod telah dibangunkan secara aktif, banyak mekanisme telah ditulis semula sepenuhnya. Tetapi kod itu ditulis mengikut piawaian 1998, dan, sebagai contoh, kurungan sudut kanan kami dipisahkan oleh ruang supaya kompilasi akan berjaya, seperti ini:

vector<vector<int> > IntV;

Pada tahun 2006, dengan keluaran platform versi 8.1, kami mula menyokong Linux dan bertukar kepada perpustakaan standard pihak ketiga STLPort. Salah satu sebab peralihan adalah untuk bekerja dengan garis lebar. Dalam kod kami, kami menggunakan std::wstring, yang berdasarkan jenis wchar_t, sepanjang. Saiznya dalam Windows ialah 2 bait, dan dalam Linux lalai ialah 4 bait. Ini membawa kepada ketidakserasian protokol binari kami antara pelanggan dan pelayan, serta pelbagai data berterusan. Menggunakan pilihan gcc, anda boleh menentukan bahawa saiz wchar_t semasa penyusunan juga adalah 2 bait, tetapi kemudian anda boleh melupakan menggunakan perpustakaan standard daripada pengkompil, kerana ia menggunakan glibc, yang seterusnya disusun untuk wchar_t 4-bait. Sebab lain ialah pelaksanaan kelas standard yang lebih baik, sokongan untuk jadual cincang, dan juga emulasi semantik pergerakan di dalam bekas, yang kami gunakan secara aktif. Dan satu lagi sebab, seperti yang mereka katakan akhir sekali, adalah prestasi rentetan. Kami mempunyai kelas kami sendiri untuk rentetan, kerana... Disebabkan kekhususan perisian kami, operasi rentetan digunakan secara meluas dan bagi kami ini adalah kritikal.

Rentetan kami adalah berdasarkan idea pengoptimuman rentetan yang dinyatakan semula pada awal 2000-an Andrei Alexandrescu. Kemudian, apabila Alexandrescu bekerja di Facebook, atas cadangannya, talian telah digunakan dalam enjin Facebook yang berfungsi pada prinsip yang sama (lihat perpustakaan kebodohan).

Talian kami menggunakan dua teknologi pengoptimuman utama:

  1. Untuk nilai pendek, penimbal dalaman dalam objek rentetan itu sendiri digunakan (tidak memerlukan peruntukan memori tambahan).
  2. Untuk semua yang lain, mekanik digunakan Salin Pada Tulis. Nilai rentetan disimpan di satu tempat, dan kaunter rujukan digunakan semasa tugasan/pengubahsuaian.

Untuk mempercepatkan kompilasi platform, kami mengecualikan pelaksanaan strim daripada varian STLPort kami (yang tidak kami gunakan), ini memberi kami kompilasi 20% lebih pantas. Selepas itu kami terpaksa membuat penggunaan terhad Tingkatkan. Boost banyak menggunakan strim, terutamanya dalam API perkhidmatannya (contohnya, untuk pengelogan), jadi kami terpaksa mengubah suainya untuk mengalih keluar penggunaan strim. Ini, seterusnya, menyukarkan kami untuk berhijrah ke versi baharu Boost.

cara ketiga

Apabila beralih ke standard C++14, kami mempertimbangkan pilihan berikut:

  1. Tingkatkan STLPort yang kami ubah suai kepada standard C++14. Pilihannya sangat sukar, kerana... sokongan untuk STLPort telah dihentikan pada tahun 2010, dan kami perlu membina semua kodnya sendiri.
  2. Peralihan kepada pelaksanaan STL lain yang serasi dengan C++14. Adalah sangat diingini bahawa pelaksanaan ini adalah untuk Windows dan Linux.
  3. Apabila menyusun untuk setiap OS, gunakan pustaka terbina dalam pengkompil yang sepadan.

Pilihan pertama ditolak mentah-mentah kerana terlalu banyak kerja.

Kami memikirkan pilihan kedua untuk beberapa lama; dianggap sebagai calon libc++, tetapi pada masa itu ia tidak berfungsi di bawah Windows. Untuk mengalihkan libc++ ke Windows, anda perlu melakukan banyak kerja - contohnya, tulis sendiri semua yang berkaitan dengan benang, penyegerakan benang dan atomicity, kerana libc++ digunakan di kawasan ini API POSIX.

Dan kami memilih cara ketiga.

Peralihan

Jadi, kami terpaksa menggantikan penggunaan STLPort dengan perpustakaan penyusun yang sepadan (Visual Studio 2015 untuk Windows, gcc 7 untuk Linux, dentang 8 untuk macOS).

Nasib baik, kod kami ditulis terutamanya mengikut garis panduan dan tidak menggunakan pelbagai helah pintar, jadi pemindahan ke perpustakaan baharu berjalan dengan lancar, dengan bantuan skrip yang menggantikan nama jenis, kelas, ruang nama dan termasuk dalam sumber fail. Penghijrahan menjejaskan 10 fail sumber (daripada 000). wchar_t telah digantikan oleh char14_t; kami memutuskan untuk meninggalkan penggunaan wchar_t, kerana char000_t mengambil 16 bait pada semua OS dan tidak merosakkan keserasian kod antara Windows dan Linux.

Terdapat beberapa pengembaraan kecil. Sebagai contoh, dalam STLPort, iterator boleh secara tersirat dilemparkan ke penunjuk kepada elemen, dan di beberapa tempat dalam kod kami ini digunakan. Di perpustakaan baharu ia tidak lagi boleh dilakukan, dan petikan ini perlu dianalisis dan ditulis semula secara manual.

Jadi, pemindahan kod selesai, kod disusun untuk semua sistem pengendalian. Sudah tiba masanya untuk ujian.

Ujian selepas peralihan menunjukkan penurunan prestasi (di sesetengah tempat sehingga 20-30%) dan peningkatan dalam penggunaan memori (sehingga 10-15%) berbanding versi lama kod. Ini, khususnya, disebabkan oleh prestasi suboptimum rentetan standard. Oleh itu, kami sekali lagi terpaksa menggunakan talian kami sendiri yang diubah suai sedikit.

Ciri menarik bagi pelaksanaan kontena dalam perpustakaan terbenam turut didedahkan: kosong (tanpa elemen) std::map dan std::set daripada perpustakaan terbina dalam memperuntukkan memori. Dan disebabkan oleh ciri pelaksanaan, di beberapa tempat dalam kod agak banyak bekas kosong jenis ini dicipta. Bekas memori standard diperuntukkan sedikit, untuk satu elemen akar, tetapi bagi kami ini ternyata kritikal - dalam beberapa senario, prestasi kami menurun dengan ketara dan penggunaan memori meningkat (berbanding dengan STLPort). Oleh itu, dalam kod kami, kami menggantikan kedua-dua jenis bekas ini daripada perpustakaan terbina dalam dengan pelaksanaannya daripada Boost, di mana bekas ini tidak mempunyai ciri sedemikian, dan ini menyelesaikan masalah dengan kelembapan dan peningkatan penggunaan memori.

Seperti yang sering berlaku selepas perubahan berskala besar dalam projek besar, lelaran pertama kod sumber tidak berfungsi tanpa masalah, dan di sini, khususnya, sokongan untuk penyahpepijatan iterator dalam pelaksanaan Windows sangat berguna. Langkah demi langkah kami bergerak ke hadapan, dan menjelang musim bunga 2017 (versi 8.3.11 1C:Enterprise) penghijrahan telah selesai.

Keputusan

Peralihan kepada standard C++14 mengambil masa kira-kira 6 bulan. Selalunya, seorang pembangun (tetapi sangat berkelayakan) bekerja pada projek itu, dan pada peringkat akhir wakil pasukan yang bertanggungjawab untuk kawasan tertentu disertai - UI, kluster pelayan, alat pembangunan dan pentadbiran, dsb.

Peralihan ini sangat memudahkan kerja kami untuk berhijrah ke versi terkini standard. Oleh itu, versi 1C:Enterprise 8.3.14 (dalam pembangunan, keluaran dijadualkan pada awal tahun depan) telah pun dipindahkan ke standard C++17.

Selepas penghijrahan, pembangun mempunyai lebih banyak pilihan. Jika sebelum ini kami mempunyai versi ubah suai STL dan satu ruang nama std kami sendiri, kini kami mempunyai kelas standard daripada perpustakaan pengkompil terbina dalam dalam ruang nama std, dalam ruang nama stdx - baris dan bekas kami dioptimumkan untuk tugas kami, dalam rangsangan - versi terbaru rangsangan. Dan pembangun menggunakan kelas yang sesuai secara optimum untuk menyelesaikan masalahnya.

Pelaksanaan "asli" pembina bergerak juga membantu dalam pembangunan (menggerakkan pembina) untuk beberapa kelas. Jika kelas mempunyai pembina bergerak dan kelas ini diletakkan dalam bekas, maka STL mengoptimumkan penyalinan elemen di dalam bekas (contohnya, apabila bekas itu dikembangkan dan perlu menukar kapasiti dan mengagihkan semula memori).

Fly di Salap

Mungkin akibat penghijrahan yang paling tidak menyenangkan (tetapi tidak kritikal) ialah kita berhadapan dengan peningkatan dalam jumlah fail obj, dan hasil penuh binaan dengan semua fail perantaraan mula mengambil 60–70 GB. Tingkah laku ini disebabkan oleh keistimewaan perpustakaan standard moden, yang menjadi kurang kritikal terhadap saiz fail perkhidmatan yang dijana. Ini tidak menjejaskan operasi aplikasi yang disusun, tetapi ia menyebabkan beberapa kesulitan dalam pembangunan, khususnya, ia meningkatkan masa penyusunan. Keperluan untuk ruang cakera kosong pada pelayan binaan dan pada mesin pembangun juga semakin meningkat. Pembangun kami bekerja pada beberapa versi platform secara selari, dan ratusan gigabait fail perantaraan kadangkala menimbulkan kesukaran dalam kerja mereka. Masalahnya tidak menyenangkan, tetapi tidak kritikal; kami telah menangguhkan penyelesaiannya buat masa ini. Kami sedang mempertimbangkan teknologi sebagai salah satu pilihan untuk menyelesaikannya membina perpaduan (khususnya, Google menggunakannya semasa membangunkan penyemak imbas Chrome).

Sumber: www.habr.com

Tambah komen