Dvejetainių failų optimizavimas C/C++ kalba naudojant GCC ir Clang

Paskutiniai pakeitimai: 14/01/2026
Autorius: Izaokas
  • Gero optimizavimo C/C++ kalboje pagrindas yra protingas derinimas -marchlygiai -O ir kai kurios saugios parinktys, pvz. -pipe.
  • Pažangūs metodai, tokie kaip LTO, PGO, OpenMP arba Graphite, gali žymiai pagerinti procesą, tačiau padidina kompiliavimo ir derinimo sudėtingumą.
  • Apsaugą stiprinančios vėliavėlės (FORTIFY, stack Protector, PIE, relro/now) sustiprina saugumą mainais už tam tikrą našumo sumažėjimą.
  • „CMake“ ir įvairūs generatoriai leidžia tvarkyti nešiojamąjį kodą GCC, Clang, MSVC ir skirtingose ​​platformose neliečiant šaltinio kodo.

C ir C++ dvejetainių failų optimizavimas naudojant GCC ir Clang

Kai pradedate žaisti su kompiliavimo parinktys C ir C++ kalbomis Lengva pasiduoti pagundai įjungti visas „kietas“ vėliavėles, kurias matote internete. Tačiau realybė tokia, kad blogas parametrų derinys gali padaryti jūsų sistemą nestabilią, sugadinti kompiliavimą arba, dar blogiau, generuoti dvejetainius failus, kurie labai subtiliai neveikia arba reikalauja informacijos išgavimo; tokiais atvejais tai gali būti naudinga. išgauti paslėptą tekstą dvejetainiuose failuose ištirti.

Šio vadovo tikslas – padėti jums praktiškai ir aiškiai suprasti, kaip Dvejetainių failų optimizavimas C/C++ kalba naudojant GCC ir Clang naudojant teisingas parinktis: nuo klasikinių -O2, -march y -pipe...iki pažangių metodų, tokių kaip LTO, PGO, OpenMP, Graphite ir saugumo stiprinimas. Taip pat pamatysite, kaip visa tai dera su CMake, MinGW/MSYS2, Visual Studio, Xcode arba Ninja, kad būtų sukurta nešiojama ir lengvai prižiūrima aplinka.

Kas yra CFLAGS ir CXXFLAGS ir kaip juos naudoti nesugadinant?

Beveik visų tipų sistemose unix (Linux, BSD ir kt.) naudojami kintamieji CFLAGS y CXXFLAGS perduoti parinktis C ir C++ kompiliatoriams. Jie nėra jokio oficialaus standarto dalis, tačiau yra tokie įprasti, kad bet kuri gerai parašyta kompiliavimo sistema („Make“, „Autotools“, „CMake“, „Meson“...) juos gerbia.

Tokiuose distribuciniuose kaip „Gentoo“ šie kintamieji apibrėžiami globaliai /etc/portage/make.confIš ten juos paveldi visi paketai, kompiliuojami naudojant „Portage“. Kitose sistemose juos galite eksportuoti į apvalkalą arba įdėti į... Makefile, Vienas scenarijus iš CMake ar panašios programos.

Gana įprasta apibrėžti CXXFLAGS pakartotinai panaudojant turinį CFLAGS ir, jei reikia, pridėkite konkrečias C++ parinktis. Pavyzdžiui: CXXFLAGS="${CFLAGS} -fno-exceptions"Svarbu ne beatodairiškai pridėti vėliavėlių, nes jos bus taikomos viskam, ką kompiliuosite.

Svarbu aiškiai pasakyti, kad Agresyvūs CFLAGS/CXXFLAGS variantai gali sugriauti surinkimusTai gali sukelti klaidų, kurias labai sunku derinti, arba netgi sulėtinti dvejetainių failų veikimą. Aukštas optimizavimo lygis ne visada lemia geresnį našumą, o kai kurios transformacijos gali išnaudoti prielaidas, kurių jūsų kodas neatitinka.

Pagrindinis optimizavimas: -march, -mtune ir -O lygiai

Bet kokio protingo koregavimo pagrindas apima tris dalis: Pasirinkite procesoriaus architektūrą, pasirinkite optimizavimo lygį ir kartais įjunkite mažus, nekenksmingus patobulinimus. kaip -pipeBeveik visa kita turėtų ateiti vėliau ir su aiškia galva.

Architektūros pasirinkimas: -march, -mtune ir company

Pasirinkimas -march=<cpu> nurodo GCC/Clang, kuri konkreti procesorių šeima valios generuoti kodąTai leidžia naudoti specifines instrukcijas (SSE, AVX, AVX2, AVX-512 ir kt.) ir koreguoti ABI. Jei esate pernelyg gudrus ir pasirinksite pernelyg modernų procesorių, dvejetainis failas senesnėse mašinose tiesiog nepasileis.

Norėdami sužinoti, ką palaiko jūsų procesorius, „Linux“ sistemoje galite peržiūrėti /proc/cpuinfo arba naudoti komandos iš paties stiliaus kompiliatoriaus gcc -Q -O2 --help=targetŠiuolaikinėse x86-64 sistemose bendriniai profiliai buvo standartizuoti, tokie kaip x86-64-v2, x86-64-v3 y x86-64-v4kurios grupuoja didėjančius instrukcijų rinkinius ir buvo palaikomos nuo GCC 11.

además de -march, egzistuoja -mtune=<cpu> „suderinti“ planavimą nuo kodo iki konkretaus modelio nenaudojant naujų instrukcijų. Jos taip pat pasitaiko ne x86 architektūrose -mcpu y -mtune Svarbios parinktys apima (ARM, PowerPC, SPARC…). x86 sistemoje -mcpu Iš tiesų jis yra pasenęs.

Dažnai naudojamas triukas yra -march=nativeTai leidžia kompiliatoriui aptikti vietinio kompiuterio procesorių ir automatiškai aktyvuoti atitinkamus plėtinius. Tai idealiai tinka aplinkose, kuriose dvejetainiai failai bus paleidžiami tik tame pačiame kompiuteryje, kuriame juos kompiliuojate, tačiau tai yra mirtini spąstai, jei generuojate paketus kitiems procesoriams.

Naujausiuose procesoriuose "Intel" Ir AMD, GCC kiekvienai šeimai įtraukia konkrečius pavadinimus, pvz. -march=rocketlake, -march=sapphirerapids, -march=znver2 o -march=znver3Šios parinktys sugrupuoja kiekvienos kartos išplėstines instrukcijas (AVX2, AVX-512, FMA ir kt.) ir leidžia išgauti nemažai naudos. techninė įranga kai žinai, kur dislokuosi.

Optimizavimo lygiai -O: kada naudoti kiekvieną

Pasirinkimas -O kontroliuoja bendrą optimizavimo lygį taikomas kodui. Kiekvienas žingsnis aktyvuoja platesnį transformacijų rinkinį, kuris turi įtakos tiek kompiliavimo laikui, tiek atminties sunaudojimui, tiek derinimo paprastumui.

  • -O0Neoptimizuotas. Tai yra numatytoji parinktis, jei nieko nenurodote. Jis greitai kompiliuojasi ir generuoja kodą, kurį labai lengva derinti, bet jis lėtas ir didelis. Idealiai tinka ankstyvajam kūrimui ir sudėtingų klaidų tyrimui.
  • -O1Pirmasis optimizavimo lygis. Taiko santykinai nebrangius patobulinimus, kurie paprastai suteikia gerą našumo padidėjimą, neapsunkindami kompiliavimo.
  • -O2: yra rekomenduojamas lygis bendram naudojimui daugumoje projektų. Tai užtikrina gerą pusiausvyrą tarp našumo, kompiliavimo laiko ir stabilumo., ir todėl tai yra reikšmė, kurią daugelis paskirstymų naudoja pagal numatytuosius nustatymus.
  • -O3: aktyvuoja visas optimizacijas -O2 Agresyvesnės transformacijos, pavyzdžiui, labai stiprus ciklo išvyniojimas arba intensyvesnė vektorizacija. Tai gali puikiai veikti kai kuriuose skaitmeniniuose koduose, tačiau taip pat yra didesnė tikimybė, kad kode bus atskleista neobjektyvumo riba (UB) arba padidės vykdomojo failo dydis.
  • -OsTai bando sumažinti dvejetainio failo dydį, teikiant pirmenybę erdvei, o ne greičiui. Tai naudinga aplinkose su sandėliavimas arba labai ribota talpykla.
  • -Oz (GCC 12+): maksimaliai sumažina dydį, toleruodamas reikšmingus našumo sumažėjimus. Naudinga labai mažiems dvejetainiams failams arba labai specifiniams scenarijams.
  • -OfastTai tarsi -O3 Jis negriežtai atitinka C/C++ standartus. Jis leidžia pažeisti kai kurias kalbos garantijas, kad būtų pasiektas didesnis našumas, ypač skaičiuojant slankiojo kablelio funkcijas. Jį reikia naudoti puikiai suprantant, ką darote.
  • -OgSkirta derinimui. Taiko tik tas optimizacijas, kurios per daug netrukdo derintuvui, ir palieka kodą vidurio taške tarp -O0 y -O1.

Lygiai aukščiau -O3 kaip -O4 o -O9 Jie visi yra dūmai ir veidrodžiaiKompiliatorius juos priima, bet viduje traktuoja kaip -O3Ten nėra jokios paslėptos magijos, tik pozavimas.

  Valve išleidžia Team Fortress 2 SDK ir sukelia revoliuciją mod bendruomenėje

Jei pastebite paslaptingai nepavykstančius sudarymo etapus, keistus gedimus arba skirtingus rezultatus, priklausomai nuo optimizavimo įrankio, geras diagnostikos žingsnis yra laikinai nusileisti iki -O1 ar netgi -O0 -g2 -ggdb gauti lengvai derinamas dvejetaines rinkmenas ir pranešti apie klaidą su naudinga informacija.

-vamzdžiai ir kitos pagrindinės parinktys

vėliava -pipe nurodo kompiliatoriui naudoti vamzdžius atmintyje Vietoj laikinų failų diske tarp kompiliavimo etapų (išankstinio apdorojimo, kompiliavimo, surinkimo). Paprastai tai šiek tiek pagreitina procesą, nors ir sunaudoja daugiau RAM. Kompiuteruose, kuriuose yra labai mažai atminties, tai gali sukelti kompiliatoriaus gedimą, todėl tokiais atvejais naudokite tai saikingai.

Kiti tradiciniai variantai, pvz. -fomit-frame-pointer Jie leidžia atlaisvinti steko rodyklės registrą, kad būtų galima sugeneruoti daugiau kodo, tačiau apsunkina derinimą su švariais atgaliniais pėdsakais. Šiuolaikinėse x86-64 architektūrose kompiliatorius tai atlieka gana gerai ir dažnai net nereikia to nustatyti rankiniu būdu.

SIMD plėtiniai, grafitas ir kilpų vektorizavimas

Šiuolaikiniai x86-64 kompiliatoriai automatiškai įjungia daugelį SIMD instrukcijų, priklausomai nuo pasirinkto procesoriaus su -marchNet ir tokiu atveju pamatysite tokias vėliavas kaip -msse2, -mavx2 arba panašius, kuriuos galima aiškiai pridėti.

Apskritai, jei naudojate -march Tai tinka; jums nereikia jo aktyvuoti rankiniu būdu. -msse, -msse2, -msse3, -mmmx o -m3dnownes jie jau įjungti pagal numatytuosius nustatymus. Prasminga juos priverstinai įdiegti tik labai konkrečiuose procesoriuose, kuriuose GCC/Clang jų neįgalina pagal numatytuosius nustatymus.

Sudėtingiems ciklams GCC apima optimizacijų rinkinį grafitaskurie remiasi ISL biblioteka. Per tokias vėliavėles kaip -ftree-loop-linear, -floop-strip-mine y -floop-block Kompiliatorius analizuoja ciklus ir gali juos pertvarkyti, kad pagerintų duomenų lokalumą ir lygiagretumą; konkrečius atvejus žr. žemo lygio C pavyzdžiai Tai padeda pritaikyti kodą šioms transformacijoms.

Šios transformacijos gali duoti gerų rezultatų naudojant sudėtingą skaitmeninį kodą, tačiau Jie nėra nekenksmingiJie gali žymiai padidinti RAM sunaudojimą kompiliavimo metu ir sukelti gedimus dideliuose projektuose, kurie nebuvo sukurti atsižvelgiant į juos. Todėl rekomenduojama juos įjungti tik konkrečiuose kodo fragmentuose arba projektuose, kuriuose jie buvo išbandyti ir įrodyta, kad veikia tinkamai.

Lygiagretumas: OpenMP, -fopenmp ir -ftree-parallelize-ciklai

Jei jūsų kodas naudoja „OpenMP“Tiek GCC, tiek Clang siūlo gana tvirtą palaikymą per šią parinktį. -fopenmpTai leidžia kodo dalis, ypač ciklus, lygiagretinti naudojant pačiame šaltinio kode esančias direktyvas, o kompiliatoriui generuoti darbą keliose gijose.

además de -fopenmpGCC apima parinktį -ftree-parallelize-loops=Nkur N Paprastai jis nustatomas pagal galimų branduolių skaičių (pavyzdžiui, naudojant $(nproc) (kūrimo scenarijuose). Tai bando automatiškai lygiagretinti ciklus, nereikalaujant pridėti rankinių direktyvų, nors sėkmė labai priklauso nuo to, kaip parašytas kodas.

  Išsamus vadovas, kaip atidaryti ir naudoti užduočių tvarkytuvę „Chromebook“ įrenginyje

Jūs turite tai nepamiršti OpenMP įjungimas visoje sistemoje gali būti labai problemiškasKai kurie projektai tam nėra pasiruošę, kiti naudoja savo lygiagretumo modelius, o kai kurie tiesiog nesugeba kompiliuotis, kai su tuo susiduria. -fopenmpProtinga tai įjungti kiekvienam projektui ar net moduliui, o ne sistemos globaliuose CFLAGS.

Ryšio laiko optimizavimas: LTO

La Ryšio laiko optimizavimas (LTO) Tai leidžia kompiliatoriui optimizavimo metu neapsiriboti vienu šaltinio failu, bet matyti visą programą susiejimo etape ir taikyti visuotinius optimizavimus visiems susijusiems objektams.

GCC sistemoje jis aktyvuojamas su -fltoir galima nurodyti keletą gijų, pavyzdžiui -flto=4arba leiskite jam aptikti branduolių skaičių su -flto=autoJei jis taip pat naudojamas -fuse-linker-plugin kartu su jungtimi auksas O įdiegus LTO papildinį į „binutils“, kompiliatorius gali išgauti LTO informaciją net iš statinių bibliotekų, dalyvaujančių susiejime.

LTO paprastai generuoja šiek tiek mažesni ir daugeliu atvejų greitesni vykdomieji failaines tai pašalina neveikiantį kodą ir leidžia įterpti modulius. Savo ruožtu, El Tiempo Kompiliavimo laikas ir atminties sunaudojimas smarkiai išauga, ypač dideliuose projektuose su tūkstančiais objektų failų.

Tokiose aplinkose kaip „Gentoo“, kur visa sistema yra perkompiliuojama iš šaltinio kodo, LTO taikymas visame pasaulyje vis dar laikomas subtiliu klausimu: Yra daug paketų, kurie vis dar neveikia su LTO. ir reikalauja jį pasirinktinai išjungti. Štai kodėl paprastai rekomenduojama jį įjungti tik konkrečiuose projektuose arba GCC/Clang versijose, kur nauda yra tikrai pastebima.

PGO: profilio valdomas optimizavimas

La Profiliu pagrįstas optimizavimas (PGO) Tai susideda iš programos kompiliavimo vieną kartą naudojant instrumentus, paleidimo su reprezentatyviais darbo krūviais, siekiant surinkti vykdymo statistiką, ir tada jos perkompiliavimo naudojant tuos profilius, kad būtų galima vadovautis optimizavimo įrankiu.

GCC tipinis srautas yra: pirmiausia kompiliuokite su -fprofile-generatepaleiskite programą (arba jos testus), kad sugeneruotumėte profilio duomenis, ir tada kompiliuoti su -fprofile-use nurodant katalogą, kuriame saugomi profilio failai. Su papildomomis parinktimis, tokiomis kaip -fprofile-correction arba išjungdami tam tikrus pranešimus (-Wno-error=coverage-mismatch) galima išvengti dažnų klaidų, atsirandančių dėl kodo pakeitimų tarp fazių; tai taip pat paprastai naudinga stebėti našumą naudojant eBPF ir perf norint gauti tikslius profilius.

Tinkamai įgyvendinus PGO gali užtikrinti daug didesnį našumo pagerėjimą nei vien tik lygio padidinimas -ONes sprendimai priimami remiantis realaus pasaulio vykdymo duomenimis, o ne bendriniais modeliais. Problema ta, kad tai sudėtingas procesas: jį reikia kartoti su kiekvienu svarbiu kodo atnaujinimu ir jis labai priklauso nuo to, ar bandymo scenarijus atspindi tikrąjį naudojimą.

Kai kurie projektai (įskaitant patį GCC tam tikruose platinimuose) jau siūlo konkrečios vėliavos ar scenarijai automatiškai aktyvuoti PGO, tačiau apskritai tai išlieka technika, skirta pažengusiems vartotojams, norintiems skirti laiko šiam procesui.

Griežtinimas: vėliava pagrįsta apsauga

Be greičio, daugelyje aplinkų dėmesys skiriamas dvejetainių failų apsaugai nuo pažeidžiamumų, net ir šiek tiek sumažėjus našumui. GCC ir šiuolaikiniai susiejimo įrankiai siūlo platų spektrą. grūdinimo parinktys kurį galima aktyvuoti iš CFLAGS/CXXFLAGS ir LDFLAGS.

Kai kurie iš labiausiai paplitusių garsas:

  • -D_FORTIFY_SOURCE=2 o =3: prideda papildomus tam tikrų libc funkcijų patikrinimus, kad vykdymo metu būtų galima aptikti buferio perpildymą.
  • -D_GLIBCXX_ASSERTIONS: aktyvuoja konteinerių ir C++ eilučių ribų patikrinimus STL kalba, aptikdamas už diapazono ribų esančias prieigas.
  • -fstack-protector-strong: įterpia kanarėles į steką, kad aptiktų jį gadinančius įrašus.
  • -fstack-clash-protection: sušvelnina atakas, pagrįstas susidūrimais tarp steko ir kitų atminties sričių.
  • -fcf-protection: prideda valdymo srauto apsaugą (pvz., nuo ROP atakų) architektūrose, kurios ją palaiko.
  • -fpie kartu su -Wl,-pie: generuoja pozicionuojamus vykdomuosius failus, būtinus efektyviam ASLR.
  • -Wl,-z,relro y -Wl,-z,nowJie sustiprina perkėlimo lentelę ir išjungia tingų susiejimą Ženklaitrukdo tam tikriems atakos vektoriams.
  „Fusion 360“ ir „Solid Edge“ bei „CATIA“: kuri CAD programinė įranga jums tinkamiausia?

Kai kurių distribucijų „sustiprintuose“ profiliuose daugelis šių parinkčių jau yra įjungtos pagal numatytuosius nustatymus. Rankinis jų aktyvavimas nesuprantant poveikio gali pastebimai sulėtinti dvejetainius failus., ypač didelėse arba labai daug atminties naudojančiose programose, tačiau pažeidžiamuose serveriuose arba jautriuose staliniuose kompiuteriuose tai paprastai yra priimtina kaina.

Pasirinkite kompiliatorių ir aplinką: GCC, Clang, MSVC, MinGW, Xcode…

Praktiškai dažnai ne tik pasirenkate vėliavas, bet ir Kurį kompiliatorių ir kurią pilną įrankių grandinę ketinate naudoti? kiekvienoje platformoje. GCC ir „Clang“ našumas paprastai yra labai panašus, o skirtumai labiau pastebimi diagnostikoje, kompiliavimo laikuose ar suderinamume su tam tikrais plėtiniais.

En Windows Turite kelis maršrutus: Vizualinė studija (MSVC) su savo įrankių rinkiniais v143, v142ir pan.; arba MinGW-w64 per MSYS2 kuri suteikia jums vietines „Windows“ GCC ir „Clang“ bibliotekas kartu su reikalingomis „Win32“ bibliotekomis. MSYS2 yra valdomas naudojant pacman ir siūlo „MinGW64“ aplinkas (pagrįstas klasikine MSVCRT) ir „UCRT64“ (su modernesne „Universal CRT“).

„macOS“ sistemoje standartinis kelias yra Xcode su clang/clang++, kur pagrindinė koncepcija yra Bazinis SDK (sistemos versija, kuriai ji yra kompiliuojama) ir Diegimo tikslas (minimali „macOS“ versija, kurioje norite, kad veiktų jūsų programa). Teisingai pakoregavus šią porą išvengiama klasikinės nelaimės, kai kompiliuojama tik naujausiai sistemos versijai ir dvejetainiai failai neveikia šiek tiek senesnėse versijose.

„Linux“ sistemoje įprasta naudoti GCC ir „Make or Ninja“Galbūt naudojant CMake kaip metageneratorių. Be to, tokios distribucijos kaip Ubuntu leidžia įdiegti kelias GCC versijas ir jas pasirinkti naudojant update-alternativespanašiai kaip naudojate jį „macOS“ sistemoje xcode-select norint perjungti iš „Xcode“.

Jei jums reikia patogios derinimo aplinkos projektams, sugeneruotiems naudojant „Make“ arba „Ninja“ (kurie yra vienos konfigūracijos), Užtemimas CDT y Visual Studio Code Tai dvi labai patogios parinktys: „CMake“ gali sukurti jums reikalingus projekto failus arba tiesiogiai su jais integruotis, kad būtų galima konfigūruoti, kompiliuoti ir derinti.

Perkeliamumas ir CMake: tas pats kodas, skirtingos įrankių grandinės

Norint, kad C/C++ projektas būtų kompiliuojamas neliečiant kodo „Windows“, „Linux“ ir „macOS“ sistemose, reikia gero abiejų derinio. CMake, galimi generatoriai ir skirtingi kompiliatoriaiIdėja yra ta, kad failas CMakeLists.txt Aprašykite projektą abstrakčiai, o „CMake“ sugeneruos atitinkamą projekto tipą kiekvienoje platformoje.

„Windows“ sistemoje galite iškviesti CMake su -G "Visual Studio 17 2022" sukurti sprendimą naudojant „msbuild“ arba su -G "Ninja" kad konsolėje būtų galima greičiau kompiliuoti. Be to, per -T v143, v142ir t. t., pasirenkate platformos įrankių rinkinį (MSVC kompiliatoriaus versiją) ir su -A x64, Win32 o arm64 Architektūrą renkatės jūs.

Naudojant „MinGW“ / „MSYS2“, įprasta naudoti yra -G "MinGW Makefiles" o -G "Ninja" ir per kintamuosius CMAKE_C_COMPILER y CMAKE_CXX_COMPILERPasirinkite, ar norite GCC, ar Clang. Šiuo atveju konfigūracijos (derinimas, išleidimas ir kt.) valdomos per -DCMAKE_BUILD_TYPE, nes „Make“ ir „Ninja“ yra vienos konfigūracijos.

„MacOS“, -G Xcode Tai suteikia jums puikų projektą derinimui IDE, o jūs galite valdyti SDK ir diegimo tikslą naudodami tokius kintamuosius kaip CMAKE_OSX_DEPLOYMENT_TARGETJei norite tik „Make“ arba „Ninja“, naudojate tuos pačius generatorius kaip ir „Linux“.

Viso to grožis yra tas, kad tinkamai sukonfigūruojus, galima palaikyti vieną kodo bazę ir nuoseklų žymų rinkinį (kartais būdingą konkrečiai platformai) ir kompiliuoti bet kokioje aplinkoje, nereikia nuolat keisti šaltinio kodo. Tačiau svarbu atsiminti pagrindinį principą: Pirmiausia įsitikinkite, kad jis veikia tinkamai, tada paspartinsime optimizavimo procesą..

Viską pamačius, bendra idėja yra laikytis vidutinio sunkumo, bet veiksmingas derinys (kažkas panašaus) -O2 -march=<cpu adecuada> -pipe (plius tam tikras pagrįstas apsaugos lygis) ir rezervuokite galingiausius metodus – LTO, PGO, grafitą, agresyvųjį OpenMP – tiems projektams ar moduliams, kuriuose patobulinimai yra iš tikrųjų išmatuoti ir priimamos jų priežiūros bei derinimo išlaidos.

Stebėkite našumą naudodami eBPF ir bpftrace
Susijęs straipsnis:
Našumo stebėjimas naudojant „eBPF“, „bpftrace“ ir „perf“ sistemoje „Linux“