Mengoptimumkan binari dalam C/C++ dengan GCC dan Clang

Kemaskini terakhir: 14/01/2026
Pengarang Ishak
  • Asas pengoptimuman yang baik dalam C/C++ adalah untuk menggabungkan secara bijak -marchtahap -O dan beberapa pilihan yang selamat seperti -pipe.
  • Teknik lanjutan seperti LTO, PGO, OpenMP atau Grafit boleh memberikan penambahbaikan yang ketara, tetapi ia meningkatkan kerumitan penyusunan dan penyahpepijatan.
  • Bendera pengerasan (FORTIFY, pelindung tindanan, PIE, relro/now) mengukuhkan keselamatan sebagai pertukaran untuk beberapa kehilangan prestasi.
  • CMake dan pelbagai penjana membolehkan anda mengekalkan kod mudah alih merentasi GCC, Clang, MSVC dan platform yang berbeza tanpa menyentuh kod sumber.

Mengoptimumkan binari C dan C++ dengan GCC dan Clang

Apabila anda mula bermain dengan pilihan kompilasi dalam C dan C++ Mudah untuk terpedaya dengan godaan untuk mendayakan semua tanda "keren" yang anda lihat dalam talian. Tetapi realitinya ialah kombinasi parameter yang buruk boleh menyebabkan sistem anda tidak stabil, merosakkan binaan, atau lebih teruk lagi, menjana binari yang gagal dengan cara yang sangat halus atau memerlukan pengekstrakan maklumat; dalam kes tersebut, ia boleh berguna. ekstrak teks tersembunyi dalam binari untuk menyiasat.

Tujuan panduan ini adalah untuk anda memahami, secara praktikal dan mudah, bagaimana Mengoptimumkan binari dalam C/C++ dengan GCC dan Clang menggunakan pilihan yang betul: daripada yang klasik -O2, -march y -pipe...kepada teknik lanjutan seperti LTO, PGO, OpenMP, Grafit dan pengerasan keselamatan. Anda juga akan melihat bagaimana semua ini sesuai dengan CMake, MinGW/MSYS2, Visual Studio, Xcode atau Ninja untuk membina persekitaran mudah alih dan boleh diselenggara.

Apakah CFLAGS dan CXXFLAGS dan bagaimana menggunakannya tanpa merosakkannya?

Dalam hampir semua jenis sistem Unix (Linux, BSD, dll.) pembolehubah digunakan CFLAGS y CXXFLAGS untuk lulus pilihan kepada pengkompil C dan C++. Ia bukan sebahagian daripada sebarang piawaian formal, tetapi ia sangat biasa sehingga mana-mana sistem binaan yang ditulis dengan baik (Make, Autotools, CMake, Meson…) menghormatinya.

Dalam taburan seperti Gentoo, pembolehubah ini ditakrifkan secara global dalam /etc/portage/make.confDari situ, ia diwarisi oleh semua pakej yang dikompilasi dengan Portage. Pada sistem lain, anda boleh mengeksportnya dalam shell atau meletakkannya dalam... Makefile, yang skrip daripada CMake atau yang serupa.

Agak biasa untuk mentakrifkan CXXFLAGS menggunakan semula kandungan CFLAGS dan, jika perlu, tambahkan sebarang pilihan khusus untuk C++. Contohnya: CXXFLAGS="${CFLAGS} -fno-exceptions"Perkara penting adalah untuk tidak menambah bendera secara sembarangan di sana, kerana ia akan digunakan pada semua yang anda kompilasi.

Adalah penting untuk menjelaskannya Pilihan agresif dalam CFLAGS/CXXFLAGS boleh merosakkan binaanIni boleh memperkenalkan pepijat yang sangat sukar untuk dinyahpepijat atau memperlahankan binari. Tahap pengoptimuman yang tinggi tidak selalunya menghasilkan prestasi yang lebih baik dan sesetengah transformasi boleh mengeksploitasi andaian bahawa kod anda tidak memenuhi.

Pengoptimuman asas: tahap -march, -mtune dan -O

Asas bagi sebarang pelarasan yang munasabah melibatkan tiga bahagian: Pilih seni bina CPU, pilih tahap pengoptimuman dan kadangkala aktifkan penambahbaikan kecil yang tidak berbahaya. sebagai -pipeHampir semua perkara lain perlu dilakukan kemudian, dan dengan fikiran yang jernih.

Memilih seni bina: -march, -mtune dan syarikat

Pilihan -march=<cpu> memberitahu GCC/Clang keluarga pemproses khusus yang mana va a menjana kodIa membenarkan penggunaan arahan khusus (SSE, AVX, AVX2, AVX-512, dll.) dan pelarasan pada ABI. Jika anda terlalu bijak dan memilih CPU yang terlalu moden, binari tidak akan dapat dibut pada mesin lama.

Untuk mengetahui apa yang disokong oleh pemproses anda, dalam Linux anda boleh merujuk /proc/cpuinfo atau gunakan arahan daripada pengkompil gaya itu sendiri gcc -Q -O2 --help=targetDalam sistem x86-64 moden, profil generik telah diseragamkan seperti x86-64-v2, x86-64-v3 y x86-64-v4kumpulan mana yang meningkatkan set arahan dan telah disokong sejak GCC 11.

Plus -march, wujud -mtune=<cpu> untuk "memperhalusi" perancangan daripada kod kepada model tertentu tanpa menggunakan arahan baharu. Ia juga muncul dalam seni bina bukan x86 -mcpu y -mtune pilihan yang berkaitan termasuk (ARM, PowerPC, SPARC…). Dalam x86, -mcpu Ia sebenarnya sudah ketinggalan zaman.

Satu helah yang biasa digunakan ialah -march=nativeIni membolehkan pengkompil mengesan CPU mesin tempatan dan mengaktifkan sambungan yang sesuai secara automatik. Ini sesuai dalam persekitaran di mana anda hanya akan menjalankan binari pada mesin yang sama di mana anda mengkompilnya, tetapi ia adalah perangkap maut jika anda menjana pakej untuk CPU lain.

Dalam pemproses terkini Intel Dan AMD, GCC menggabungkan nama khusus untuk setiap keluarga, seperti -march=rocketlake, -march=sapphirerapids, -march=znver2 o -march=znver3Pilihan ini mengumpulkan arahan lanjutan (AVX2, AVX-512, FMA, dll.) bagi setiap generasi dan membolehkan anda memanfaatkannya dengan secukupnya. perkakasan apabila anda tahu di mana anda akan ditempatkan.

Tahap pengoptimuman -O: bila hendak menggunakan setiap satu

Pilihan -O mengawal tahap pengoptimuman keseluruhan digunakan pada kod. Setiap langkah mengaktifkan satu set transformasi yang lebih luas, yang memberi kesan kepada masa kompilasi dan penggunaan memori serta kemudahan penyahpepijatan.

  • -O0Tidak dioptimumkan. Ini ialah pilihan lalai jika anda tidak menentukan apa-apa. Ia dikompilasi dengan cepat dan menjana kod yang sangat mudah untuk dinyahpepijat, tetapi ia perlahan dan besar. Sesuai untuk pembangunan awal dan menyiasat pepijat yang kompleks.
  • -O1Tahap pengoptimuman pertama. Menggunakan penambahbaikan yang agak murah yang biasanya memberikan peningkatan prestasi yang baik tanpa menjadikan kompilasi terlalu berat.
  • -O2: ialah tahap yang disyorkan untuk kegunaan umum dalam kebanyakan projek. Ia mencapai keseimbangan yang baik antara prestasi, masa kompilasi dan kestabilan., dan itulah sebabnya ia merupakan nilai yang digunakan oleh banyak taburan secara lalai.
  • -O3: mengaktifkan semua pengoptimuman -O2 Transformasi yang lebih agresif, seperti pembongkaran gelung yang sangat kuat atau pengvektoran yang lebih intensif. Ini boleh berfungsi dengan baik dalam beberapa kod berangka, tetapi ia juga lebih berkemungkinan untuk mendedahkan UB dalam kod atau mengembangkan saiz boleh laku.
  • -OsIni cuba mengurangkan saiz binari dengan mengutamakan ruang berbanding kelajuan. Ia berguna dalam persekitaran dengan penyimpanan atau cache yang sangat terhad.
  • -Oz (GCC 12+): mengambil penjimatan saiz secara ekstrem, menerima penurunan prestasi yang ketara. Berguna untuk binari yang sangat kecil atau senario yang sangat spesifik.
  • -OfastIa seperti -O3 Ia tidak mematuhi piawaian C/C++ sepenuhnya. Ia membolehkan anda melanggar beberapa jaminan bahasa untuk mendapatkan prestasi tambahan, terutamanya dalam pengiraan titik apungan. Anda mesti menggunakannya dengan pemahaman penuh tentang apa yang anda lakukan.
  • -OgDireka untuk penyahpepijatan. Ia hanya menggunakan pengoptimuman yang tidak terlalu mengganggu penyahpepijat dan meninggalkan kod pada titik tengah antara -O0 y -O1.

Tahap di atas -O3 sebagai -O4 o -O9 Semuanya asap dan cerminPengkompil menerimanya tetapi secara dalaman melayannya sebagai -O3Tiada sihir tersembunyi di sana, hanya sekadar berpura-pura.

  Valve mengeluarkan Team Fortress 2 SDK dan merevolusikan komuniti mod

Jika anda mula melihat binaan yang gagal secara misteri, ranap pelik atau hasil yang berbeza bergantung pada pengoptimum, langkah diagnostik yang baik adalah turun sementara ke -O1 atau bahkan -O0 -g2 -ggdb untuk mendapatkan binari yang mudah dinyahpepijat dan melaporkan pepijat dengan maklumat berguna.

-paip dan pilihan asas lain

bendera -pipe memberitahu pengkompil untuk menggunakan paip dalam ingatan Daripada fail sementara pada cakera antara fasa kompilasi (prapemprosesan, kompilasi, pemasangan). Ia biasanya menjadikan proses agak pantas, walaupun ia menggunakan lebih banyak RAM. Pada mesin dengan memori yang sangat sedikit, ia boleh menyebabkan sistem ranap, jadi gunakannya dengan berhati-hati dalam kes tersebut.

Pilihan tradisional lain seperti -fomit-frame-pointer Ia membolehkan anda membebaskan daftar penunjuk tindanan untuk menjana lebih banyak kod, tetapi ia menjadikan penyahpepijatan dengan jejak balik yang bersih lebih sukar. Pada seni bina x86-64 moden, pengkompil mengendalikan perkara ini dengan agak baik, dan selalunya ia tidak perlu ditetapkan secara manual.

Sambungan SIMD, Grafit dan vektorisasi gelung

Pengkompil moden untuk x86-64 secara automatik mendayakan banyak arahan SIMD bergantung pada CPU yang dipilih dengan -marchWalaupun begitu, anda akan melihat bendera seperti -msse2, -mavx2 atau yang serupa yang boleh ditambah secara eksplisit.

Secara amnya, jika anda menggunakan -march Ini sesuai; anda tidak perlu mengaktifkannya secara manual. -msse, -msse2, -msse3, -mmmx o -m3dnowkerana ia telah diaktifkan secara lalai. Adalah masuk akal untuk memaksanya pada CPU yang sangat spesifik di mana GCC/Clang tidak mendayakannya secara lalai.

Untuk gelung kompleks, GCC merangkumi set pengoptimuman grafityang bergantung pada perpustakaan ISL. Melalui bendera seperti -ftree-loop-linear, -floop-strip-mine y -floop-block Pengkompil menganalisis gelung dan boleh menstrukturkannya semula untuk menambah baik lokaliti data dan selarikan; untuk kes tertentu, lihat contoh peringkat rendah C Ia membantu menyesuaikan kod untuk transformasi ini.

Transformasi ini boleh menghasilkan keputusan yang baik dalam kod berangka yang berat, tetapi Mereka tidak berbahayaIa boleh meningkatkan penggunaan RAM dengan ketara semasa penyusunan dan menyebabkan ranap sistem dalam projek besar yang tidak direka bentuk dengan mengambil kira perkara tersebut. Oleh itu, adalah disyorkan untuk mendayakannya hanya dalam coretan kod atau projek tertentu yang telah diuji dan terbukti berfungsi dengan betul.

Paralelisme: OpenMP, -fopenmp dan -ftree-selarikan-gelung

Jika kod anda menggunakan OpenmpKedua-dua GCC dan Clang menawarkan sokongan yang agak kukuh melalui pilihan tersebut -fopenmpIni membolehkan bahagian kod, terutamanya gelung, diselaraskan menggunakan arahan dalam kod sumber itu sendiri, dan untuk pengkompil menjana kerja dalam berbilang thread.

Plus -fopenmpGCC merangkumi pilihan -ftree-parallelize-loops=N, di mana N Ia biasanya ditetapkan kepada bilangan teras yang tersedia (contohnya menggunakan $(nproc) (dalam skrip binaan). Ini cuba untuk menyelaraskan gelung secara automatik tanpa perlu menambah arahan manual, walaupun kejayaan sangat bergantung pada cara kod ditulis.

  Panduan lengkap untuk membuka dan menggunakan pengurus tugas pada Chromebook

Anda harus ingat bahawa Mengaktifkan OpenMP secara global merentasi keseluruhan sistem boleh menjadi sangat bermasalahSesetengah projek tidak bersedia untuknya, yang lain menggunakan model serentak mereka sendiri, dan sesetengahnya gagal dikompilasi apabila mereka menemuinya. -fopenmpPerkara yang wajar dilakukan adalah mendayakannya setiap projek atau setiap modul, bukan dalam CFLAGS global sistem.

Pengoptimuman masa pautan: LTO

La Pengoptimuman Masa Pautan (LTO) Ia membolehkan pengkompil tidak terhad kepada satu fail sumber sahaja semasa pengoptimuman, tetapi untuk melihat keseluruhan program dalam fasa pautan dan menggunakan pengoptimuman global kepada semua objek yang terlibat.

Dalam GCC ia diaktifkan dengan -fltodan beberapa utas boleh ditentukan, contohnya -flto=4, atau biarkan ia mengesan bilangan teras dengan -flto=autoJika ia juga digunakan -fuse-linker-plugin bersama-sama dengan penghubung emas Dan dengan plugin LTO yang dipasang dalam binutils, pengkompil boleh mengekstrak maklumat LTO walaupun daripada pustaka statik yang terlibat dalam pengikatan.

LTO biasanya menjana agak lebih kecil dan, dalam banyak kes, boleh laku yang lebih pantaskerana ia menghapuskan kod mati dan membenarkan penyelitan antara modul. Sebagai balasannya, el tiempo Masa kompilasi dan penggunaan memori meningkat mendadak, terutamanya dalam projek besar dengan beribu-ribu fail objek.

Dalam persekitaran seperti Gentoo, di mana keseluruhan sistem dikompilasi semula dari sumber, penggunaan LTO secara global masih dianggap sebagai perkara yang sensitif: Terdapat banyak pakej yang masih tidak berfungsi dengan baik dengan LTO. dan memerlukannya dilumpuhkan secara selektif. Itulah sebabnya biasanya disyorkan untuk mendayakannya hanya dalam projek tertentu atau binaan GCC/Clang di mana manfaatnya benar-benar ketara.

PGO: Pengoptimuman Berpandu Profil

La Pengoptimuman berpandukan profil (PGO) Ia terdiri daripada menyusun program sekali dengan instrumentasi, menjalankannya dengan beban kerja perwakilan untuk mengumpul statistik pelaksanaan, dan kemudian menyusun semulanya menggunakan profil tersebut untuk membimbing pengoptimum.

Dalam GCC, aliran tipikal ialah: kompilasi pertama dengan -fprofile-generatejalankan program (atau ujiannya) untuk menjana data profil, dan kemudian menyusun dengan -fprofile-use menunjuk ke direktori tempat fail profil disimpan. Dengan pilihan tambahan seperti -fprofile-correction atau dengan melumpuhkan pemberitahuan tertentu (-Wno-error=coverage-mismatch) ralat kerap yang terhasil daripada perubahan kod antara fasa boleh dielakkan; ia juga biasanya berguna Pantau prestasi dengan eBPF dan perf untuk mendapatkan profil yang tepat.

Apabila dilaksanakan dengan betul, PGO boleh memberikan peningkatan prestasi yang jauh lebih besar daripada sekadar meningkatkan tahap -OKerana ia membuat keputusan berdasarkan data pelaksanaan dunia sebenar, bukan model generik. Masalahnya ialah ia merupakan proses yang rumit: ia perlu diulang dengan setiap kemas kini kod yang berkaitan, dan ia sangat bergantung pada senario ujian yang mewakili penggunaan sebenar.

Sesetengah projek (termasuk GCC sendiri dalam pengedaran tertentu) sudah menawarkan bendera atau skrip tertentu untuk mengaktifkan PGO secara automatik, tetapi secara amnya ia kekal sebagai teknik untuk pengguna lanjutan yang sanggup melaburkan masa dalam proses tersebut.

Pengerasan: keselamatan berasaskan bendera

Selain kelajuan, banyak persekitaran menumpukan pada pengerasan binari terhadap kelemahan, walaupun dengan mengorbankan beberapa kehilangan prestasi. GCC dan penghubung moden menawarkan pelbagai pilihan yang baik pilihan pengerasan yang boleh diaktifkan daripada CFLAGS/CXXFLAGS dan LDFLAGS.

Antara yang paling biasa bunyi:

  • -D_FORTIFY_SOURCE=2 o =3: menambah semakan tambahan pada fungsi libc tertentu untuk mengesan limpahan penimbal semasa masa jalan.
  • -D_GLIBCXX_ASSERTIONS: mengaktifkan pemeriksaan sempadan pada kontena dan rentetan C++ dalam STL, mengesan akses di luar julat.
  • -fstack-protector-strong: memasukkan burung kenari ke dalam tindanan untuk mengesan penulisan yang merosakkannya.
  • -fstack-clash-protection: mengurangkan serangan berdasarkan perlanggaran antara tindanan dan kawasan memori lain.
  • -fcf-protection: menambah perlindungan aliran kawalan (contohnya, terhadap serangan ROP) pada seni bina yang menyokongnya.
  • -fpie bersama dengan -Wl,-pie: menjana fail boleh laku yang boleh diposisikan, diperlukan untuk ASLR yang berkesan.
  • -Wl,-z,relro y -Wl,-z,nowMereka mengeraskan meja penempatan semula dan melumpuhkan pengikatan malas simbolmenghalang vektor serangan tertentu.
  Fusion 360 lwn Solid Edge lwn CATIA: Perisian CAD Mana Yang Terbaik untuk Anda?

Profil "Diperkeraskan" bagi sesetengah pengedaran sudah mempunyai banyak pilihan ini yang diaktifkan secara lalai. Mengaktifkannya secara manual tanpa memahami kesannya boleh menyebabkan binari yang lebih perlahan., terutamanya dalam aplikasi yang besar atau sangat intensif memori, tetapi pada pelayan yang terdedah atau desktop sensitif, ia biasanya merupakan harga yang berpatutan.

Pilih pengkompil dan persekitaran: GCC, Clang, MSVC, MinGW, Xcode…

Dalam praktiknya, anda sering bukan sahaja memilih bendera, tetapi Pengkompil yang manakah dan rantaian alat lengkap yang manakah akan anda gunakan? pada setiap platform. GCC dan Clang biasanya sangat serupa dari segi prestasi, dan perbezaannya lebih ketara dalam diagnostik, masa kompilasi atau keserasian dengan sambungan tertentu.

En Windows Anda mempunyai beberapa laluan: Visual Studio (MSVC) dengan set peralatan mereka v143, v142dll.; atau MinGW-w64 melalui MSYS2 yang memberikan anda Windows GCC dan Clang asli berserta pustaka Win32 yang diperlukan. MSYS2 diuruskan dengan pacman dan menawarkan persekitaran MinGW64 (berdasarkan MSVCRT klasik) dan UCRT64 (dengan Universal CRT, lebih moden).

Pada macOS, laluan standard ialah Xcode dengan clang/clang++, di mana konsep utamanya ialah SDK Asas (versi sistem yang mana ia dikompilasi) dan Sasaran Penggunaan (versi macOS minimum yang anda mahu aplikasi anda jalankan). Melaraskan pasangan ini dengan betul dapat mengelakkan bencana klasik iaitu hanya mengkompilasi untuk versi sistem terkini dan menyebabkan binari anda gagal berjalan pada versi yang sedikit lebih lama.

Dalam Linux, perkara biasa yang perlu dilakukan ialah menggunakan GCC dan Buatan atau NinjaMungkin menggunakan CMake sebagai meta-penjana. Selain itu, pengedaran seperti Ubuntu membolehkan anda memasang berbilang versi GCC dan memilihnya dengan update-alternatives, sama seperti cara anda menggunakannya dalam macOS xcode-select untuk bertukar daripada Xcode.

Jika anda memerlukan persekitaran penyahpepijatan yang selesa untuk projek yang dijana dengan Make atau Ninja (yang merupakan konfigurasi tunggal), CDT Eclipse y Kod Studio Visual Ini adalah dua pilihan yang sangat berguna: CMake boleh menghasilkan fail projek yang anda perlukan atau mengintegrasikannya secara langsung dengan fail tersebut untuk mengkonfigurasi, mengkompil dan menyahpepijat.

Kemudahalihan dan CMake: kod yang sama, rantaian alat yang berbeza

Mengkompilasi projek C/C++ tanpa menyentuh kod pada Windows, Linux dan macOS memerlukan gabungan kedua-duanya yang baik. CMake, penjana yang tersedia dan pengkompil yang berbezaIdeanya ialah fail itu CMakeLists.txt Huraikan projek secara abstrak dan CMake akan menjana jenis projek yang sesuai pada setiap platform.

Pada Windows anda boleh menggunakan CMake dengan -G "Visual Studio 17 2022" untuk menghasilkan penyelesaian dengan msbuild, atau dengan -G "Ninja" untuk mempunyai binaan yang lebih pantas daripada konsol. Selain itu, melalui -T v143, v142dll., anda memilih Set Alat Platform (versi pengkompil MSVC) dan dengan -A x64, Win32 o arm64 Anda memilih seni bina.

Dengan MinGW/MSYS2, perkara biasa yang perlu digunakan ialah -G "MinGW Makefiles" o -G "Ninja" dan, melalui pembolehubah CMAKE_C_COMPILER y CMAKE_CXX_COMPILERPilih sama ada anda mahukan GCC atau Clang. Dalam kes ini, konfigurasi (Debug, Release, dll.) dikawal melalui -DCMAKE_BUILD_TYPE, memandangkan Make dan Ninja ialah konfigurasi tunggal.

Pada macOS, -G Xcode Ia memberi anda projek yang sempurna untuk penyahpepijatan dalam IDE, dan anda boleh mengawal SDK dan Sasaran Pelaksanaan dengan pembolehubah seperti CMAKE_OSX_DEPLOYMENT_TARGETJika anda hanya mahukan Make atau Ninja, anda menggunakan penjana yang sama seperti dalam Linux.

Keindahan semua ini ialah, jika dikonfigurasikan dengan betul, anda boleh mengekalkan satu pangkalan kod dan satu set bendera yang konsisten (kadangkala khusus platform) dan mengkompil dalam mana-mana persekitaran tanpa perlu sentiasa mengubah suai kod sumber. Walau bagaimanapun, adalah penting untuk mengingati prinsip utama: Pertama, pastikan ia berfungsi dengan betul, kemudian kami akan mempercepatkan proses pengoptimuman..

Dengan semua yang dilihat, idea umum adalah untuk kekal dengan gabungan yang sederhana tetapi berkesan (sesuatu seperti ini) -O2 -march=<cpu adecuada> -pipe ditambah beberapa pengerasan yang munasabah) dan menyimpan senjata besar —LTO, PGO, Grafit, OpenMP yang agresif— untuk projek atau modul yang mana penambahbaikan benar-benar diukur dan kos penyelenggaraan dan penyahpepijatan yang dibawanya diterima.

Pantau prestasi dengan eBPF dan bpftrace
artikel berkaitan:
Memantau prestasi dengan eBPF, bpftrace dan perf dalam Linux