Teknik apa yang dapat digunakan untuk mempercepat waktu kompilasi C ++?


249

Teknik apa yang dapat digunakan untuk mempercepat waktu kompilasi C ++?

Pertanyaan ini muncul dalam beberapa komentar untuk Stack Overflow pertanyaan gaya pemrograman C ++ , dan saya tertarik untuk mendengar ide apa yang ada.

Saya telah melihat pertanyaan terkait, Mengapa kompilasi C ++ memakan waktu begitu lama? , tapi itu tidak memberikan banyak solusi.


1
Bisakah Anda memberi kami beberapa konteks? Atau apakah Anda mencari jawaban yang sangat umum?
Pirolistik

1
Sangat mirip dengan pertanyaan ini: stackoverflow.com/questions/364240/…
Adam Rosenfield

Jawaban umum. Saya punya basis kode yang sangat besar yang ditulis oleh banyak orang. Gagasan tentang bagaimana menyerang itu akan bagus. Dan juga, saran untuk menjaga kompilasi cepat untuk kode yang baru ditulis akan menarik.
Scott Langham

Perhatikan bahwa sering kali bagian yang relevan dari waktu pembuatan tidak digunakan oleh kompiler tetapi oleh skrip build
thi gg

1
Saya membaca halaman ini dan tidak melihat pengukuran. Saya menulis sebuah skrip shell kecil yang menambahkan stempel waktu untuk setiap baris input yang diterimanya, jadi saya bisa mem-pipe dalam doa 'make'. Ini memungkinkan saya melihat target mana yang paling mahal, kompilasi total atau waktu tautan, dll. Hanya dengan membandingkan cap waktu. Jika Anda mencoba pendekatan ini, ingat bahwa cap waktu akan tidak akurat untuk pembuatan paralel.
John P

Jawaban:


257

Teknik bahasa

Pimpl Idiom

Lihatlah idiom Pimpl di sini , dan di sini , juga dikenal sebagai pointer buram atau menangani kelas. Tidak hanya mempercepat kompilasi, itu juga meningkatkan keamanan pengecualian bila dikombinasikan dengan fungsi swap non-lempar . Ungkapan Pimpl memungkinkan Anda mengurangi ketergantungan antara tajuk dan mengurangi jumlah kompilasi yang perlu dilakukan.

Deklarasi Teruskan

Jika memungkinkan, gunakan deklarasi maju . Jika kompiler hanya perlu tahu bahwa itu SomeIdentifieradalah struct atau pointer atau apa pun, jangan sertakan keseluruhan definisi, memaksa kompiler untuk melakukan lebih banyak pekerjaan daripada yang diperlukan. Ini dapat memiliki efek berjenjang, membuat cara ini lebih lambat dari yang seharusnya.

Aliran I / O secara khusus dikenal untuk memperlambat build. Jika Anda membutuhkannya dalam file header, coba #include <iosfwd>bukan <iostream>dan #include <iostream>header dalam file implementasi saja. The <iosfwd>sundulan memegang deklarasi maju saja. Sayangnya header standar lainnya tidak memiliki header deklarasi masing-masing.

Lebih suka pass-by-reference daripada pass-by-value dalam tanda tangan fungsi. Ini akan menghilangkan kebutuhan untuk # mencantumkan definisi tipe masing-masing dalam file header dan Anda hanya perlu meneruskan-menyatakan jenisnya. Tentu saja, lebih suka referensi const ke referensi non-const untuk menghindari bug yang tidak jelas, tetapi ini merupakan masalah untuk pertanyaan lain.

Kondisi Penjaga

Gunakan kondisi penjaga untuk menjaga agar file header tidak dimasukkan lebih dari satu kali dalam satu unit terjemahan.

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

Dengan menggunakan baik pragma dan ifndef, Anda mendapatkan portabilitas dari solusi makro biasa, serta optimasi kecepatan kompilasi yang beberapa kompiler dapat lakukan di hadapan pragma oncearahan.

Mengurangi saling ketergantungan

Semakin modular dan tidak saling tergantung desain kode Anda secara umum, semakin jarang Anda harus mengkompilasi ulang semuanya. Anda juga dapat mengurangi jumlah pekerjaan yang harus dilakukan oleh kompiler pada setiap blok secara bersamaan, berdasarkan fakta bahwa ia memiliki lebih sedikit untuk dilacak.

Opsi penyusun

Header Terkompilasi

Ini digunakan untuk mengkompilasi bagian umum dari header yang dimasukkan satu kali untuk banyak unit terjemahan. Kompiler mengkompilasinya sekali, dan menyimpan status internalnya. Status itu kemudian dapat dimuat dengan cepat untuk mendapatkan kepala mulai dalam mengkompilasi file lain dengan set header yang sama.

Berhati-hatilah karena Anda hanya memasukkan hal-hal yang jarang berubah dalam tajuk yang dikompilasi sebelumnya, atau Anda dapat melakukan pembangunan kembali penuh lebih sering daripada yang diperlukan. Ini adalah tempat yang baik untuk header STL dan perpustakaan lainnya menyertakan file.

ccache adalah utilitas lain yang memanfaatkan teknik caching untuk mempercepat.

Gunakan Paralelisme

Banyak kompiler / dukungan IDE menggunakan beberapa core / CPU untuk melakukan kompilasi secara bersamaan. Di GNU Make (biasanya digunakan dengan GCC), gunakan -j [N]opsi. Di Visual Studio, ada opsi di bawah preferensi untuk memungkinkannya membangun beberapa proyek secara paralel. Anda juga dapat menggunakan /MPopsi untuk paralellisme tingkat file, bukan hanya paralellisme tingkat proyek.

Utilitas paralel lainnya:

Gunakan Level Optimalisasi yang Lebih Rendah

Semakin kompiler mencoba mengoptimalkan, semakin sulit kerjanya.

Perpustakaan yang Dibagikan

Memindahkan kode yang kurang sering diubah ke perpustakaan dapat mengurangi waktu kompilasi. Dengan menggunakan perpustakaan bersama ( .soatau .dll), Anda dapat mengurangi waktu penautan juga.

Dapatkan Komputer yang Lebih Cepat

Lebih banyak RAM, hard drive yang lebih cepat (termasuk SSD), dan lebih banyak CPU / core semuanya akan membuat perbedaan dalam kecepatan kompilasi.


11
Header yang dikompilasi sebelumnya tidak sempurna. Efek samping dari penggunaannya adalah Anda mendapatkan lebih banyak file yang disertakan dari yang diperlukan (karena setiap unit kompilasi menggunakan header yang dikompilasi sebelumnya), yang dapat memaksa kompilasi ulang penuh lebih sering daripada yang diperlukan. Hanya sesuatu yang perlu diingat.
jalf

8
Dalam kompiler modern, #ifndef sama cepatnya dengan #pragma sekali (selama guard include ada di bagian atas file). Jadi tidak ada untungnya bagi #pragma sekali dalam hal kecepatan kompilasi
jalf

7
Bahkan jika Anda baru saja VS 2005, bukan 2008, Anda dapat menambahkan / MP beralih dalam opsi kompilasi untuk mengaktifkan pembangunan paralel di tingkat .cpp.
macbirdie

6
SSD sangat mahal ketika jawaban ini ditulis, tetapi hari ini mereka adalah pilihan terbaik ketika mengkompilasi C ++. Anda mengakses banyak file kecil saat kompilasi ;. Itu membutuhkan banyak IOPS, yang diberikan SSD.
MSalters

14
Lebih suka pass-by-reference daripada pass-by-value dalam tanda tangan fungsi. Ini akan menghilangkan kebutuhan untuk # mencantumkan definisi tipe masing-masing dalam file header Ini salah , Anda tidak perlu memiliki tipe lengkap untuk mendeklarasikan fungsi yang melewati nilai, Anda hanya perlu tipe lengkap untuk mengimplementasikan atau menggunakan fungsi itu , tetapi dalam kebanyakan kasus (kecuali jika Anda hanya meneruskan panggilan), Anda tetap membutuhkan definisi itu.
David Rodríguez - dribeas

43

Saya bekerja pada proyek STAPL yang merupakan perpustakaan C ++ yang sangat templated. Sekali-sekali, kita harus meninjau kembali semua teknik untuk mengurangi waktu kompilasi. Di sini, saya telah merangkum teknik yang kami gunakan. Beberapa teknik ini sudah tercantum di atas:

Menemukan bagian yang paling memakan waktu

Meskipun tidak ada korelasi yang terbukti antara panjang simbol dan waktu kompilasi, kami telah mengamati bahwa ukuran simbol rata-rata yang lebih kecil dapat meningkatkan waktu kompilasi pada semua kompiler. Jadi, tujuan pertama Anda adalah menemukan simbol terbesar dalam kode Anda.

Metode 1 - Urutkan simbol berdasarkan ukuran

Anda dapat menggunakan nmperintah untuk membuat daftar simbol berdasarkan ukurannya:

nm --print-size --size-sort --radix=d YOUR_BINARY

Dalam perintah ini, --radix=dAnda dapat melihat ukuran dalam angka desimal (standarnya adalah hex). Sekarang dengan melihat simbol terbesar, kenali apakah Anda dapat memecah kelas yang sesuai dan mencoba mendesain ulang dengan memfaktorkan bagian-bagian yang tidak templated di kelas dasar, atau dengan membagi kelas menjadi beberapa kelas.

Metode 2 - Urutkan simbol berdasarkan panjangnya

Anda dapat menjalankan nmperintah biasa dan menyalurkannya ke skrip favorit Anda ( AWK , Python , dll.) Untuk mengurutkan simbol berdasarkan panjangnya . Berdasarkan pengalaman kami, metode ini mengidentifikasi masalah terbesar yang membuat kandidat lebih baik daripada metode 1.

Metode 3 - Gunakan Templight

" Templight adalah alat berbasis dentang untuk profil waktu dan memori konsumsi instantiasi template dan untuk melakukan sesi debugging interaktif untuk mendapatkan introspeksi ke dalam proses instantiasi template".

Anda dapat menginstal Templight dengan memeriksa LLVM dan Dentang ( instruksi ) dan menerapkan templight patch di atasnya. Pengaturan default untuk LLVM dan Dentang adalah pada debug dan pernyataan, dan ini dapat memengaruhi waktu kompilasi Anda secara signifikan. Sepertinya Templight membutuhkan keduanya, jadi Anda harus menggunakan pengaturan default. Proses menginstal LLVM dan Dentang harus memakan waktu sekitar satu jam atau lebih.

Setelah menerapkan tambalan Anda dapat menggunakan yang templight++terletak di folder build yang Anda tentukan saat instalasi untuk mengkompilasi kode Anda.

Pastikan itu templight++ada di PATH Anda. Sekarang untuk mengkompilasi, tambahkan sakelar berikut ke CXXFLAGSdalam Makefile Anda atau ke opsi baris perintah Anda:

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

Atau

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

Setelah kompilasi selesai, Anda akan memiliki .trace.memory.pbf dan .trace.pbf yang dihasilkan di folder yang sama. Untuk memvisualisasikan jejak ini, Anda bisa menggunakan Templight Tools yang bisa mengonversikannya ke format lain. Ikuti instruksi ini untuk menginstal konversi cahaya. Kami biasanya menggunakan output callgrind. Anda juga dapat menggunakan output GraphViz jika proyek Anda kecil:

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

File callgrind yang dihasilkan dapat dibuka menggunakan kcachegrind di mana Anda dapat melacak instantiasi yang paling memakan waktu / memori.

Mengurangi jumlah contoh template

Meskipun tidak ada solusi tepat untuk mengurangi jumlah contoh template, ada beberapa panduan yang dapat membantu:

Kelas refactor dengan lebih dari satu argumen templat

Misalnya, jika Anda memiliki kelas,

template <typename T, typename U>
struct foo { };

dan keduanya Tdan Udapat memiliki 10 opsi yang berbeda, Anda telah meningkatkan kemungkinan contoh template kelas ini menjadi 100. Salah satu cara untuk menyelesaikan ini adalah dengan abstrak bagian umum dari kode ke kelas yang berbeda. Metode lain adalah dengan menggunakan inversi warisan (membalikkan hierarki kelas), tetapi pastikan bahwa tujuan desain Anda tidak terganggu sebelum menggunakan teknik ini.

Refactor kode non-templated ke unit terjemahan individual

Dengan menggunakan teknik ini, Anda dapat mengkompilasi bagian umum satu kali dan menautkannya dengan TU lainnya (unit terjemahan) nanti.

Gunakan instantiasi templat eksternal (sejak C ++ 11)

Jika Anda tahu semua instantiasi yang mungkin dari suatu kelas, Anda dapat menggunakan teknik ini untuk mengkompilasi semua kasus di unit terjemahan yang berbeda.

Misalnya, di:

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

Kita tahu bahwa kelas ini dapat memiliki tiga kemungkinan instance:

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

Masukkan yang di atas dalam unit terjemahan dan gunakan kata kunci extern dalam file header Anda, di bawah definisi kelas:

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

Teknik ini dapat menghemat waktu Anda jika Anda menyusun tes yang berbeda dengan serangkaian instantiasi yang umum.

CATATAN: MPICH2 mengabaikan instantiasi eksplisit pada titik ini dan selalu mengkompilasi kelas instantiated di semua unit kompilasi.

Gunakan bangunan kesatuan

Seluruh ide di balik pembangunan kesatuan adalah untuk memasukkan semua file .cc yang Anda gunakan dalam satu file dan mengkompilasi file itu hanya sekali. Dengan menggunakan metode ini, Anda dapat menghindari memulihkan bagian umum dari file yang berbeda dan jika proyek Anda menyertakan banyak file umum, Anda mungkin akan menghemat akses disk juga.

Sebagai contoh, mari kita asumsikan Anda memiliki tiga file foo1.cc, foo2.cc, foo3.ccdan mereka semua termasuk tupledari STL . Anda dapat membuat foo-all.ccyang terlihat seperti:

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

Anda mengkompilasi file ini hanya sekali dan berpotensi mengurangi instantiations umum di antara tiga file. Sulit untuk memprediksi secara umum apakah peningkatan itu signifikan atau tidak. Tetapi satu fakta yang jelas adalah bahwa Anda akan kehilangan paralelisme di build Anda (Anda tidak bisa lagi mengkompilasi ketiga file pada saat yang sama).

Lebih lanjut, jika ada dari file-file ini yang mengambil banyak memori, Anda mungkin benar-benar kehabisan memori sebelum kompilasi selesai. Pada beberapa kompiler, seperti GCC , ini mungkin ICE (Internal Compiler Error) kompiler Anda karena kekurangan memori. Jadi jangan gunakan teknik ini kecuali Anda tahu semua pro dan kontra.

Header yang dikompilasi sebelumnya

Precompiled header (PCHs) dapat menghemat banyak waktu dalam kompilasi dengan mengkompilasi file header Anda ke representasi perantara yang dikenali oleh kompiler. Untuk menghasilkan file header yang sudah dikompilasi, Anda hanya perlu mengkompilasi file header Anda dengan perintah kompilasi biasa. Misalnya, di GCC:

$ g++ YOUR_HEADER.hpp

Ini akan menghasilkan YOUR_HEADER.hpp.gch file( .gchadalah ekstensi untuk file PCH di GCC) di folder yang sama. Ini berarti bahwa jika Anda memasukkan YOUR_HEADER.hppdalam beberapa file lain, kompiler akan menggunakan Anda YOUR_HEADER.hpp.gchdaripada YOUR_HEADER.hppdi folder yang sama sebelumnya.

Ada dua masalah dengan teknik ini:

  1. Anda harus memastikan bahwa file header yang dikompilasi stabil dan tidak akan berubah ( Anda selalu dapat mengubah makefile Anda )
  2. Anda hanya dapat memasukkan satu PCH per unit kompilasi (pada sebagian besar kompiler). Ini berarti bahwa jika Anda memiliki lebih dari satu file header untuk dikompilasi, Anda harus memasukkannya dalam satu file (misalnya, all-my-headers.hpp). Tetapi itu berarti Anda harus memasukkan file baru di semua tempat. Untungnya, GCC punya solusi untuk masalah ini. Gunakan -includedan berikan file header yang baru. Anda dapat koma memisahkan file yang berbeda menggunakan teknik ini.

Sebagai contoh:

g++ foo.cc -include all-my-headers.hpp

Gunakan ruang nama yang tidak disebutkan namanya atau anonim

Ruang nama tanpa nama (alias ruang nama anonim) dapat mengurangi ukuran biner yang dihasilkan secara signifikan. Ruang nama yang tidak disebutkan namanya menggunakan tautan internal, artinya simbol yang dihasilkan dalam ruang nama tersebut tidak akan terlihat oleh TU lain (unit terjemahan atau kompilasi). Compiler biasanya menghasilkan nama unik untuk ruang nama yang tidak disebutkan namanya. Ini berarti bahwa jika Anda memiliki file foo.hpp:

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

Dan Anda kebetulan memasukkan file ini dalam dua TU (dua file .cc dan kompilasi secara terpisah). Dua contoh templat foo tidak akan sama. Ini melanggar Aturan Satu Definisi (ODR). Untuk alasan yang sama, menggunakan ruang nama tanpa nama tidak disarankan dalam file header. Jangan ragu untuk menggunakannya di .ccfile Anda untuk menghindari simbol muncul di file biner Anda. Dalam beberapa kasus, mengubah semua detail internal untuk .ccfile menunjukkan pengurangan 10% dalam ukuran biner yang dihasilkan.

Mengubah opsi visibilitas

Dalam kompiler yang lebih baru Anda dapat memilih simbol Anda untuk terlihat atau tidak terlihat dalam Dynamic Shared Objects (DSOs). Idealnya, mengubah visibilitas dapat meningkatkan kinerja kompiler, tautan waktu optimasi (LTO), dan ukuran biner yang dihasilkan. Jika Anda melihat file header STL di GCC Anda dapat melihat bahwa itu digunakan secara luas. Untuk mengaktifkan pilihan visibilitas, Anda perlu mengubah kode Anda per fungsi, per kelas, per variabel dan yang lebih penting per kompiler.

Dengan bantuan visibilitas, Anda dapat menyembunyikan simbol yang Anda anggap pribadi dari objek yang dibagikan yang dihasilkan. Pada GCC Anda dapat mengontrol visibilitas simbol dengan melewatkan default atau disembunyikan ke -visibilityopsi kompiler Anda. Ini dalam beberapa hal mirip dengan namespace yang tidak disebutkan namanya tetapi dengan cara yang lebih rumit dan mengganggu.

Jika Anda ingin menentukan visibilitas per kasus, Anda harus menambahkan atribut berikut ke fungsi, variabel, dan kelas Anda:

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

Visibilitas default di GCC adalah default (publik), yang berarti bahwa jika Anda mengkompilasi metode di atas sebagai shared library ( -shared), foo2dan kelas foo3tidak akan terlihat di TU lain ( foo1dan foo4akan terlihat). Jika Anda mengompilasinya -visibility=hiddenmaka hanya foo1akan terlihat. Bahkan foo4akan disembunyikan.

Anda dapat membaca lebih lanjut tentang visibilitas di wiki GCC .


33

Saya akan merekomendasikan artikel ini dari "Game from Within, Indie Game Design and Programming":

Memang, mereka cukup lama - Anda harus menguji kembali semuanya dengan versi terbaru (atau versi yang tersedia untuk Anda), untuk mendapatkan hasil yang realistis. Apa pun itu, itu adalah sumber ide yang bagus.


17

Salah satu teknik yang bekerja dengan baik bagi saya di masa lalu: jangan mengkompilasi beberapa file sumber C ++ secara independen, tetapi menghasilkan satu file C ++ yang mencakup semua file lainnya, seperti ini:

// myproject_all.cpp
// Automatically generated file - don't edit this by hand!
#include "main.cpp"
#include "mainwindow.cpp"
#include "filterdialog.cpp"
#include "database.cpp"

Tentu saja ini berarti Anda harus mengkompilasi ulang semua kode sumber yang disertakan jika ada sumber yang berubah, sehingga pohon dependensi semakin buruk. Namun, kompilasi banyak file sumber sebagai satu unit terjemahan lebih cepat (setidaknya dalam percobaan saya dengan MSVC dan GCC) dan menghasilkan binari yang lebih kecil. Saya juga curiga bahwa kompiler diberikan lebih banyak potensi untuk optimisasi (karena dapat melihat lebih banyak kode sekaligus).

Teknik ini pecah dalam berbagai kasus; sebagai contoh, kompiler akan menebus jika dua atau lebih file sumber mendeklarasikan fungsi global dengan nama yang sama. Saya tidak dapat menemukan teknik ini dijelaskan dalam jawaban lain, itu sebabnya saya sebutkan di sini.

Untuk apa nilainya, Proyek KDE menggunakan teknik yang sama persis ini sejak 1999 untuk membangun binari yang dioptimalkan (mungkin untuk rilis). Beralih ke skrip konfigurasi build dipanggil --enable-final. Karena ketertarikan arkeologis, saya menggali postingan yang mengumumkan fitur ini: http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2


2
Saya tidak yakin apakah itu benar-benar hal yang sama, tapi saya kira menyalakan "optimasi seluruh program" di VC ++ ( msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx ) harus memiliki efek yang sama pada kinerja runtime dari apa yang Anda sarankan. Namun, kompilasi waktu pasti bisa lebih baik dalam pendekatan Anda!
Philipp

1
@Frerich: Anda menggambarkan bangunan Unity yang disebutkan dalam jawaban OJ. Saya juga telah melihat mereka disebut build massal dan build master.
idbrii

Jadi bagaimana UB dibandingkan dengan WPO / LTCG?
paulm

Ini berpotensi bermanfaat hanya untuk kompilasi satu kali, tidak selama pengembangan di mana Anda siklus antara mengedit, membangun dan menguji. Di dunia modern, empat inti adalah norma, mungkin beberapa tahun kemudian jumlah inti lebih banyak. Jika compiler dan linker tidak dapat memanfaatkan beberapa utas, maka daftar file mungkin dapat dipecah menjadi <core-count> + Nsublists yang dikompilasi paralel di mana Nada beberapa integer yang sesuai (tergantung pada memori sistem dan bagaimana mesin itu digunakan).
FooF

15

Ada seluruh buku tentang topik ini, yang berjudul Desain Perangkat Lunak C ++ Skala Besar (ditulis oleh John Lakos).

Templat buku pra-tanggal, sehingga untuk isi buku itu menambahkan "menggunakan templat, dapat membuat kompiler lebih lambat".


Buku ini sering disebut dalam topik semacam ini tetapi bagi saya itu jarang dalam informasi. Ini pada dasarnya menyatakan untuk menggunakan deklarasi maju sebanyak mungkin dan memisahkan dependensi. Itu sedikit menyatakan yang jelas selain itu menggunakan idiom jerawat memiliki kelemahan runtime.
gast128

@ gast128 Saya pikir intinya adalah menggunakan idiom pengkodean yang memungkinkan kompilasi ulang tambahan, yaitu, sehingga jika Anda mengubah sedikit sumber di suatu tempat Anda tidak perlu mengkompilasi ulang semuanya.
ChrisW

15

Saya hanya akan menautkan ke jawaban saya yang lain: Bagaimana cara Anda mengurangi waktu kompilasi, dan menghubungkan waktu untuk proyek Visual C ++ (asli C ++)? . Poin lain yang ingin saya tambahkan, tetapi yang sering menyebabkan masalah adalah menggunakan header yang sudah dikompilasi. Tapi tolong, hanya gunakan untuk bagian yang hampir tidak pernah berubah (seperti header toolkit GUI). Jika tidak, mereka akan dikenakan biaya lebih banyak waktu daripada pada akhirnya Anda menghemat.

Opsi lain adalah, ketika Anda bekerja dengan GNU make, untuk mengaktifkan -j<N>opsi:

  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

Saya biasanya memilikinya 3karena saya punya dual core di sini. Kemudian akan menjalankan kompiler secara paralel untuk unit terjemahan yang berbeda, asalkan tidak ada ketergantungan di antara mereka. Menghubungkan tidak dapat dilakukan secara paralel, karena hanya ada satu proses tautan yang menghubungkan semua file objek.

Tetapi linker itu sendiri dapat di-threaded, dan inilah yang dilakukan ELF linker. Ini dioptimalkan ulir kode C ++ yang dikatakan untuk menautkan file objek ELF besarnya lebih cepat dari yang lama (dan sebenarnya dimasukkan ke dalam binutils ).GNU gold ld


Baiklah. Maaf pertanyaan itu tidak muncul ketika saya mencari.
Scott Langham

kamu tidak perlu minta maaf. itu untuk Visual C ++. pertanyaan Anda tampaknya untuk kompiler apa pun. jadi tidak apa-apa :)
Johannes Schaub - litb

12

Inilah beberapa:

  • Gunakan semua core prosesor dengan memulai pekerjaan multi-kompilasi ( make -j2adalah contoh yang baik).
  • Matikan atau optimisasi yang lebih rendah (misalnya, GCC jauh lebih cepat -O1daripada -O2atau -O3).
  • Gunakan tajuk yang dikompilasi sebelumnya .

12
FYI, saya menemukan biasanya lebih cepat untuk memulai lebih banyak proses daripada inti. Sebagai contoh pada sistem quad core, saya biasanya menggunakan -j8, bukan -j4. Alasan untuk ini adalah bahwa ketika satu proses diblokir pada I / O, yang lain dapat dikompilasi.
Tn. Fooz

@ MrFooz: Saya menguji ini beberapa tahun yang lalu dengan mengkompilasi kernel Linux (dari penyimpanan RAM) pada i7-2700k (4 core, 8 thread, saya menetapkan pengganda konstan). Saya lupa hasil terbaik yang tepat, tetapi -j12untuk sekitar -j18itu jauh lebih cepat daripada -j8, seperti yang Anda sarankan. Saya bertanya-tanya berapa banyak inti yang dapat Anda miliki sebelum bandwidth memori menjadi faktor pembatas ...
Mark K Cowan

@MarkKCowan itu tergantung pada banyak faktor. Komputer yang berbeda memiliki bandwidth memori yang sangat berbeda. Dengan prosesor kelas atas akhir-akhir ini, dibutuhkan beberapa inti untuk menjenuhkan bus memori. Juga, ada keseimbangan antara I / O dan CPU. Beberapa kode sangat mudah dikompilasi, kode lain bisa lambat (misalnya dengan banyak template). Aturan praktis saya saat ini adalah -jdengan 2x jumlah inti aktual.
Tuan Fooz

11

Setelah Anda menerapkan semua trik kode di atas (meneruskan deklarasi, mengurangi penyertaan header ke minimum dalam header publik, mendorong sebagian besar detail di dalam file implementasi dengan Pimpl ...) dan tidak ada lagi yang bisa diperoleh secara bahasa, pertimbangkan sistem build Anda . Jika Anda menggunakan Linux, pertimbangkan untuk menggunakan distcc (didistribusikan compiler) dan ccache (cache compiler).

Yang pertama, distcc, mengeksekusi langkah preprocessor secara lokal dan kemudian mengirimkan output ke kompiler pertama yang tersedia di jaringan. Ini membutuhkan versi kompilator dan pustaka yang sama di semua node yang dikonfigurasi dalam jaringan.

Yang terakhir, ccache, adalah cache kompiler. Sekali lagi mengeksekusi preprocessor dan kemudian memeriksa dengan database internal (diadakan di direktori lokal) jika file preprocessor sudah dikompilasi dengan parameter kompiler yang sama. Jika ya, itu hanya muncul biner dan output dari jalankan pertama dari kompiler.

Keduanya dapat digunakan pada saat yang sama, sehingga jika ccache tidak memiliki salinan lokal, ia dapat mengirimkannya melalui net ke node lain dengan distcc, atau jika tidak, ia hanya dapat menyuntikkan solusi tanpa proses lebih lanjut.


2
Saya tidak berpikir bahwa distcc memerlukan versi pustaka yang sama pada semua node yang dikonfigurasi. distcc hanya melakukan kompilasi jarak jauh, bukan tautannya. Ini juga mengirimkan kode yang sudah diproses sebelumnya melalui kabel, jadi header yang tersedia di sistem jarak jauh tidak masalah.
Frerich Raabe

9

Ketika saya keluar dari perguruan tinggi, kode C ++ layak-produksi pertama yang nyata yang saya lihat memiliki kode rahasia #fndef ... # arahan di antara mereka di mana header didefinisikan. Saya bertanya kepada orang yang menulis kode tentang hal-hal yang menyeluruh ini dengan cara yang sangat naif dan diperkenalkan ke dunia pemrograman skala besar.

Kembali ke titik, menggunakan arahan untuk mencegah duplikasi definisi header adalah hal pertama yang saya pelajari ketika harus mengurangi waktu kompilasi.


1
tua tapi emas. terkadang yang jelas terlupakan.
alcor

1
'termasuk penjaga'
gast128

8

RAM lebih banyak.

Seseorang berbicara tentang drive RAM di jawaban lain. Saya melakukan ini dengan 80286 dan Turbo C ++ (menunjukkan usia) dan hasilnya sangat fenomenal. Seperti halnya hilangnya data saat mesin mogok.


dalam DOS Anda tidak dapat memiliki banyak memori
phuclv

6

Gunakan deklarasi maju di mana Anda bisa. Jika deklarasi kelas hanya menggunakan pointer atau referensi ke suatu tipe, Anda bisa meneruskannya mendeklarasikannya dan menyertakan header untuk tipe tersebut dalam file implementasi.

Sebagai contoh:

// T.h
class Class2; // Forward declaration

class T {
public:
    void doSomething(Class2 &c2);
private:
    Class2 *m_Class2Ptr;
};

// T.cpp
#include "Class2.h"
void Class2::doSomething(Class2 &c2) {
    // Whatever you want here
}

Lebih sedikit termasuk cara yang jauh lebih sedikit pekerjaan untuk preprosesor jika Anda cukup melakukannya.


Bukankah ini hanya masalah ketika tajuk yang sama dimasukkan dalam beberapa unit terjemahan? Jika hanya ada satu unit terjemahan (seperti yang sering terjadi ketika templat digunakan) maka ini tampaknya tidak berdampak.
AlwaysLearning

1
Jika hanya ada satu unit terjemahan, mengapa repot-repot meletakkannya di header? Bukankah lebih masuk akal untuk hanya meletakkan konten dalam file sumber? Bukankah inti dari tajuknya adalah bahwa ia cenderung dimasukkan oleh lebih dari satu file sumber?
Evan Teran


5

Menggunakan

#pragma once

di bagian atas file header, jadi jika mereka dimasukkan lebih dari satu kali dalam unit terjemahan, teks header hanya akan dimasukkan dan diurai satu kali.


2
Meskipun didukung secara luas, #pragma sekali tidak standar. Lihat en.wikipedia.org/wiki/Pragma_once
ChrisInEdmonton

7
Dan hari-hari ini, penjaga termasuk reguler memiliki efek yang sama. Selama mereka berada di bagian atas file, kompiler sepenuhnya mampu memperlakukan mereka sebagai #pragma sekali
jalf


4
  • Tingkatkan komputer Anda

    1. Dapatkan quad core (atau sistem dual-quad)
    2. Dapatkan BANYAK RAM.
    3. Gunakan drive RAM untuk secara drastis mengurangi penundaan I / O file. (Ada perusahaan yang membuat IDE dan SATA RAM drive yang bertindak seperti hard drive).
  • Maka Anda memiliki semua saran khas lainnya

    1. Gunakan tajuk yang dikompilasi jika tersedia.
    2. Kurangi jumlah sambungan antara bagian-bagian proyek Anda. Mengubah satu file tajuk biasanya tidak perlu mengharuskan kompilasi ulang seluruh proyek Anda.

4

Saya punya ide tentang menggunakan drive RAM . Ternyata untuk proyek saya itu tidak membuat banyak perbedaan setelah semua. Tapi mereka masih sangat kecil. Cobalah! Saya akan tertarik mendengar betapa banyak membantu.


Hah. Mengapa seseorang memilih ini? Saya akan mencobanya besok.
Scott Langham

1
Saya berharap downvote karena tidak pernah membuat perbedaan besar. Jika Anda memiliki cukup RAM yang tidak digunakan, OS akan dengan cerdas menggunakannya sebagai cache disk.
MSalters

1
@MSalters - dan berapa banyak yang akan "cukup"? Aku tahu bahwa itu adalah teori, tapi untuk beberapa alasan menggunakan ramdrive tidak benar-benar memberikan dorongan yang signifikan. Go figure ...
Vilx-

1
cukup untuk mengkompilasi proyek Anda dan masih menyimpan input dan file sementara. Jelas sisi dalam GB akan tergantung langsung pada ukuran proyek Anda. Perlu dicatat bahwa pada cache file OS'es lama (khususnya WinXP) cukup malas, meninggalkan RAM yang tidak digunakan.
MSalters

Tentunya ram drive lebih cepat jika file sudah di ram daripada melakukan sejumlah IO lambat dulu, lalu mereka di ram? (naik-ulangi untuk file yang telah berubah - tulis kembali ke disk dll).
paulm

3

Tautan dinamis (.so) bisa jauh lebih cepat daripada tautan statis (.a). Terutama ketika Anda memiliki drive jaringan yang lambat. Ini karena Anda memiliki semua kode dalam file .a yang perlu diproses dan ditulis. Selain itu, file yang dapat dieksekusi yang jauh lebih besar perlu dituliskan ke disk.


tautan dinamis mencegah banyak jenis optimisasi tautan-waktu sehingga hasilnya mungkin lebih lambat dalam banyak kasus
phuclv

3

Bukan tentang waktu kompilasi, tetapi tentang waktu pembuatan:

  • Gunakan ccache jika Anda harus membangun kembali file yang sama ketika Anda mengerjakan buildfile Anda

  • Gunakan ninja-build bukan make. Saat ini saya sedang menyusun proyek dengan ~ 100 file sumber dan semuanya di-cache oleh ccache. buat kebutuhan 5 menit, ninja kurang dari 1.

Anda dapat membuat file ninja dari cmake with -GNinja.


3

Di mana Anda menghabiskan waktu Anda? Apakah Anda terikat CPU? Memori terikat? Disk terikat? Bisakah Anda menggunakan lebih banyak core? Lebih banyak RAM? Apakah Anda memerlukan RAID? Apakah Anda hanya ingin meningkatkan efisiensi sistem Anda saat ini?

Di bawah gcc / g ++, sudahkah Anda melihat ccache ? Akan sangat membantu jika Anda melakukan make clean; makebanyak hal.


2

Hard disk lebih cepat.

Kompiler menulis banyak (dan mungkin besar) file ke disk. Bekerja dengan SSD, bukannya hard disk biasa dan waktu kompilasi jauh lebih rendah.



2

Pembagian jaringan akan secara drastis memperlambat pembangunan Anda, karena latensi pencariannya tinggi. Untuk sesuatu seperti Boost, itu membuat perbedaan besar bagi saya, meskipun drive berbagi jaringan kami cukup cepat. Waktu untuk mengkompilasi mainan Program peningkatan berjalan dari sekitar 1 menit menjadi 1 detik ketika saya beralih dari jaringan berbagi ke SSD lokal.


2

Jika Anda memiliki prosesor multicore, baik Visual Studio (2005 dan yang lebih baru) serta GCC mendukung kompilasi multi-prosesor. Ini adalah sesuatu untuk diaktifkan jika Anda memiliki perangkat keras, pasti.


2
@Fellman, Lihat beberapa jawaban lain - gunakan opsi -j #.
strager

1

Meskipun bukan "teknik", saya tidak tahu bagaimana proyek Win32 dengan banyak file sumber dikompilasi lebih cepat daripada proyek kosong "Hello World" saya. Jadi, saya harap ini membantu orang seperti saya.

Di Visual Studio, satu opsi untuk menambah waktu kompilasi adalah Penautan Tambahan ( / INCREMENTAL ). Ini tidak kompatibel dengan Pembuatan Kode Waktu-Link ( / LTCG ), jadi ingatlah untuk menonaktifkan penautan tambahan saat melakukan rilis build.


1
menonaktifkan Pembuatan Kode Tautan-waktu bukanlah saran yang bagus karena itu menonaktifkan banyak optimisasi. Anda harus mengaktifkan hanya /INCREMENTALdalam mode debug
phuclv

1

Dimulai dengan Visual Studio 2017 Anda memiliki kemampuan untuk memiliki beberapa metrik kompilator tentang apa yang membutuhkan waktu.

Tambahkan parameter tersebut ke C / C ++ -> Baris perintah (Opsi Tambahan) di jendela properti proyek: /Bt+ /d2cgsummary /d1reportTime

Anda dapat memiliki lebih banyak informasi dalam posting ini .


0

Menggunakan tautan dinamis alih-alih yang statis membuat Anda lebih cepat membuat kompiler.

Jika Anda menggunakan t Cmake, aktifkan properti:

set(BUILD_SHARED_LIBS ON)

Bangun Rilis, menggunakan tautan statis dapat lebih mengoptimalkan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.