- C/C++ hea optimeerimise alus on mõistlik kombineerimine
-marchtasemed-Oja mõned ohutud valikud, näiteks-pipe. - Täiustatud tehnikad nagu LTO, PGO, OpenMP või Graphite võivad pakkuda märkimisväärseid edusamme, kuid suurendavad kompileerimise ja silumise keerukust.
- Tugevdavad lipud (FORTIFY, stack protector, PIE, relro/now) tugevdavad turvalisust vastutasuks mõningase jõudluse kaotusega.
- CMake ja mitmesugused generaatorid võimaldavad teil hallata kaasaskantavat koodi GCC-s, Clangis, MSVC-s ja erinevatel platvormidel ilma lähtekoodi puutumata.

Kui hakkad mängima kompileerimisvalikud C ja C++ keeles On lihtne langeda kiusatusse lubada kõikvõimalikud "lahedad" lipud, mida internetis näeb. Tegelikkus on aga see, et halb parameetrite kombinatsioon võib muuta teie süsteemi ebastabiilseks, rikkuda ehitustöid või, mis veelgi hullem, genereerida binaarfaile, mis ebaõnnestuvad väga peenel moel või vajavad teabe hankimist; sellistel juhtudel võib see olla kasulik. eralda binaarfailidest peidetud tekst uurima.
Selle juhendi eesmärk on aidata teil praktiliselt ja arusaadavalt mõista, kuidas Binaarfailide optimeerimine C/C++-s GCC ja Clangi abil õigete valikute kasutamine: klassikalistest -O2, -march y -pipe...täiustatud tehnikateni nagu LTO, PGO, OpenMP, Graphite ja turvalisuse tugevdamine. Samuti näete, kuidas see kõik sobib kokku CMake'i, MinGW/MSYS2, Visual Studio, Xcode'i või Ninjaga, et luua kaasaskantav ja hooldatav keskkond.
Mis on CFLAGS ja CXXFLAGS ning kuidas neid kasutada ilma asju sassi ajamata?
Peaaegu igat tüüpi süsteemides Unix (Linux, BSD jne) kasutatakse muutujaid CFLAGS y CXXFLAGS valikute edasiandmiseks C ja C++ kompilaatorile. Need ei kuulu ühegi ametliku standardi hulka, kuid on nii levinud, et iga hästi kirjutatud ehitussüsteem (Make, Autotools, CMake, Meson...) austab neid.
Sellistes distributsioonides nagu Gentoo on need muutujad globaalselt defineeritud /etc/portage/make.confSealt edasi pärivad need kõik Portage'iga kompileeritud paketid. Teistes süsteemides saate need kesta eksportida või panna... Makefile, Üks käsikiri CMake'ilt või sarnaselt.
On üsna tavaline defineerida CXXFLAGS sisu taaskasutamine CFLAGS ja vajadusel lisage C++ jaoks spetsiifilisi valikuid. Näiteks: CXXFLAGS="${CFLAGS} -fno-exceptions"Oluline on mitte validamatult lippe lisada, sest need rakendatakse kõigele, mida kompileerid.
Oluline on selgelt öelda, et CFLAGS/CXXFLAGS-i agressiivsed valikud võivad ehitusi rikkudaSee võib tekitada vigu, mida on väga raske siluda, või isegi aeglustada binaarfaile. Kõrge optimeerimistase ei taga alati paremat jõudlust ja mõned teisendused võivad ära kasutada eeldusi, millele teie kood ei vasta.
Põhiline optimeerimine: -march, -mtune ja -O tasemed
Iga mõistliku kohandamise aluseks on kolm osa: Valige protsessori arhitektuur, valige optimeerimise tase ja mõnikord aktiveerige väikesed, ohutud täiustused. kui -pipePeaaegu kõik muu peaks tulema hiljem ja selge peaga.
Arhitektuuri valimine: -march, -mtune ja company
Valik -march=<cpu> annab GCC/Clangile teada, millisest konkreetsest protsessoriperekonnast see räägib läheb koodi genereeridaSee lubab kasutada spetsiifilisi käske (SSE, AVX, AVX2, AVX-512 jne) ja ABI kohandusi. Kui oled liiga nutikas ja valid liiga moodsa protsessori, siis binaarfail vanematel masinatel lihtsalt ei käivitu.
Linuxis protsessori tugifunktsioonide väljaselgitamiseks võite vaadata /proc/cpuinfo või kasutada käsud stiili kompilaatorilt endalt gcc -Q -O2 --help=targetTänapäevastes x86-64 süsteemides on üldised profiilid standardiseeritud, näiteks x86-64-v2, x86-64-v3 y x86-64-v4mis rühmitavad suurenevaid käskude komplekte ja on toetatud alates GCC 11-st.
Lisaks -march, on olemas -mtune=<cpu> planeerimist "täpsustada" koodist konkreetse mudelini ilma uusi juhiseid kasutamata. Need esinevad ka mitte-x86 arhitektuurides -mcpu y -mtune Olulised valikud on näiteks (ARM, PowerPC, SPARC…). X86-s -mcpu Tegelikult on see vananenud.
Levinud trikk on -march=nativeSee võimaldab kompilaatoril tuvastada kohaliku masina protsessori ja automaatselt aktiveerida vastavad laiendused. See on ideaalne keskkondades, kus binaarfaile käitatakse ainult samas masinas, kus neid kompileeritakse, kuid see on surmalõks, kui genereerite pakette teistele protsessoritele.
Viimastes töötlejates Intel Ja AMD, GCC sisaldab iga perekonna jaoks spetsiifilisi nimetusi, näiteks -march=rocketlake, -march=sapphirerapids, -march=znver2 o -march=znver3Need valikud koondavad iga põlvkonna täpsemad juhised (AVX2, AVX-512, FMA jne) ja võimaldavad teil neist üsna palju kasu saada. riistvara kui tead, kuhu sa lähed.
Optimeerimistasemed -O: millal igaüht neist kasutada
Valik -O kontrollib üldist optimeerimise taset rakendatakse koodile. Iga samm aktiveerib laiema hulga teisendusi, mis mõjutavad nii kompileerimisaega kui ka mälukasutust ja silumise lihtsust.
-O0Optimeerimata. See on vaikevalik, kui te midagi ei määra. See kompileerub kiiresti ja genereerib koodi, mida on väga lihtne siluda, kuid see on aeglane ja mahukas. Ideaalne varajaseks arenduseks ja keerukate vigade uurimiseks.-O1Optimeerimise esimene tase. Rakendab suhteliselt odavaid täiustusi, mis tavaliselt pakuvad korralikku jõudluse kasvu ilma kompileerimist liiga raskeks muutmata.-O2: on enamikus projektides üldiseks kasutamiseks soovitatav tase. See leiab hea tasakaalu jõudluse, kompileerimisaja ja stabiilsuse vahel.ja seepärast kasutavad paljud distributsioonid vaikimisi seda väärtust.-O3: aktiveerib kõik optimeeringud-O2Agressiivsemad teisendused, näiteks väga tugev tsükli lahtiharutamine või intensiivsem vektoriseerimine. See võib mõnes numbrilises koodis suurepäraselt toimida, kuid see võib ka tõenäolisemalt paljastada koodis ebatäpse sisu või suurendada käivitatava faili suurust.-OsSee püüab vähendada binaarfaili suurust, seades ruumi kiirusest kõrgemale. See on kasulik keskkondades, kus ladustamine või väga piiratud vahemälu.-Oz(GCC 12+): viib mahu kokkuhoiu äärmuseni, aktsepteerides olulisi jõudluse languseid. Kasulik väga väikeste binaarfailide või väga spetsiifiliste stsenaariumide korral.-OfastSee on nagu-O3See ei järgi rangelt C/C++ standardeid. See võimaldab teil mõningaid keelegarantiisid rikkuda, et saavutada lisajõudlust, eriti ujukomaarvutustes. Selle kasutamisel peate täielikult aru saama, mida teete.-OgLoodud silumiseks. See rakendab ainult optimeeringuid, mis ei sega silurit liiga palju ja jätab koodi keskpunktiks-O0y-O1.
Ülemised tasemed -O3 kui -O4 o -O9 Nad kõik on suits ja peeglidKompilaator aktsepteerib neid, aga käsitleb neid sisemiselt kui -O3Seal pole mingit varjatud maagiat, ainult teesklemine.
Kui hakkate nägema müstiliselt ebaõnnestunud ehitusi, kummalisi krahhe või optimeerijast olenevalt erinevaid tulemusi, on hea diagnostiline samm ajutiselt alla minna -O1 o incluso -O0 -g2 -ggdb et hankida hõlpsasti silutavaid binaarfaile ja teatada veast koos kasuliku teabega.
-torud ja muud põhilised valikud
lipp -pipe käsib kompilaatoril mälus torusid kasutada Kompileerimisetappide (eeltöötlus, kompileerimine, assembler) vahel kettal asuvate ajutiste failide asemel. Tavaliselt muudab see protsessi mõnevõrra kiiremaks, kuigi see tarbib rohkem RAM-i. Väga vähese mäluga masinatel võib see põhjustada kompilaatori krahhi, seega kasutage seda sellistel juhtudel säästlikult.
Teised traditsioonilised valikud, näiteks -fomit-frame-pointer Need võimaldavad vabastada pinu pointeri registrit, et genereerida rohkem koodi, kuid muudavad puhaste tagasijälgedega silumise keerulisemaks. Tänapäevastel x86-64 arhitektuuridel saab kompilaator sellega üsna hästi hakkama ja sageli pole seda isegi vaja käsitsi seadistada.
SIMD laiendused, grafiit ja silmusvektoriseerimine
Kaasaegsed x86-64 kompilaatorid lubavad automaatselt paljusid SIMD-käsklusi, olenevalt valitud protsessorist -marchSellegipoolest näete lippe nagu -msse2, -mavx2 või sarnaseid, mida saab selgesõnaliselt lisada.
Üldiselt, kui kasutate -march See sobib; te ei pea seda käsitsi aktiveerima. -msse, -msse2, -msse3, -mmmx o -m3dnowsest need on juba vaikimisi lubatud. Mõistlik on neid sundida ainult väga kindlatele protsessoritele, kus GCC/Clang neid vaikimisi ei luba.
Komplekssete tsüklite puhul sisaldab GCC optimeerimiste komplekti Grafiitmis tuginevad ISL-i teeki. Selliste lippude kaudu nagu -ftree-loop-linear, -floop-strip-mine y -floop-block Kompilaator analüüsib tsükleid ja saab neid ümber struktureerida, et parandada andmete lokaalsust ja paralleelsust; konkreetsete juhtumite kohta vaata madala C-taseme näited See aitab koodi nende teisenduste jaoks kohandada.
Need teisendused võivad anda häid tulemusi raskes numbrilises koodis, aga Nad ei ole kahjutudNeed võivad kompileerimise ajal märkimisväärselt suurendada RAM-i tarbimist ja põhjustada krahhe suurtes projektides, mis pole neid silmas pidades loodud. Seetõttu on soovitatav need lubada ainult teatud koodilõikudes või projektides, kus need on testitud ja nende korrektne toimimine on tõestatud.
Paralleelsus: OpenMP, -fopenmp ja -ftree-parallelize-tsüklid
Kui teie kood kasutab OpenmpNii GCC kui ka Clang pakuvad valiku kaudu üsna kindlat tuge -fopenmpSee võimaldab koodiosasid, eriti tsükleid, paralleelselt rakendada lähtekoodi direktiivide abil ning kompilaatoril genereerida töö mitmes lõimes.
Lisaks -fopenmpGCC sisaldab valikut -ftree-parallelize-loops=NKus N Tavaliselt määratakse see saadaolevate südamike arvu järgi (näiteks kasutades $(nproc) (ehitusskriptides). See püüab tsükleid automaatselt paralleelseks muuta ilma käsitsi direktiive lisamata, kuigi edu sõltub suuresti koodi kirjutamisviisist.
Pea meeles, et OpenMP globaalse lubamine kogu süsteemis võib olla väga problemaatilineMõned projektid pole selleks ette valmistatud, teised kasutavad omaenda samaaegsusmudeleid ja mõned lihtsalt ei kompileeru, kui nad sellega kokku puutuvad. -fopenmpMõistlik on see lubada projekti või isegi mooduli kaupa, mitte süsteemi globaalsetes CFLAGS-ides.
Lingi aja optimeerimine: LTO
La Lingiaja optimeerimine (LTO) See võimaldab kompilaatoril optimeerimisel mitte piirduda ühe lähtekoodifailiga, vaid näha linkimisfaasis kogu programmi ja rakendada globaalseid optimeeringuid kõigile kaasatud objektidele.
Pärsia lahe koostöös aktiveeritakse see koos -fltoja saab määrata mitu niiti, näiteks -flto=4või laske sel tuvastada südamike arvu -flto=autoKui seda ka kasutatakse -fuse-linker-plugin koos linkeriga kuld Ja kui binutilsi on installitud LTO plugin, saab kompilaator LTO teavet ekstraheerida isegi sidumisega seotud staatilistest teekidest.
LTO genereerib tavaliselt mõnevõrra väiksemad ja paljudel juhtudel kiiremad käivitatavad failidsest see kõrvaldab surnud koodi ja võimaldab moodulite vahelist teksti sisestamist. Vastutasuks aeg Kompileerimisajad ja mälukasutus suurenevad hüppeliselt, eriti suurtes projektides, kus on tuhandeid objektifaile.
Keskkondades nagu Gentoo, kus kogu süsteem kompileeritakse lähtekoodist uuesti, peetakse LTO globaalset rakendamist endiselt delikaatseks küsimuseks: On palju pakette, mis LTO-ga endiselt hästi ei tööta. ja nõuavad selle valikulist keelamist. Seetõttu on tavaliselt soovitatav see lubada ainult teatud projektides või GCC/Clangi järkudes, kus kasu on tõeliselt märgatav.
PGO: profiilipõhine optimeerimine
La Profiilipõhine optimeerimine (PGO) See seisneb programmi ühekordses kompileerimises instrumentidega, selle käivitamises representatiivsete töökoormustega täitmisstatistika kogumiseks ja seejärel programmi uuesti kompileerimises, kasutades neid profiile optimeerija juhtimiseks.
GCC-s on tüüpiline voog järgmine: esmalt kompileeri -fprofile-generatekäivitage programm (või selle testid) profiiliandmete genereerimiseks ja seejärel kompileeri koos -fprofile-use osutades kataloogile, kuhu profiilifailid on salvestatud. Lisavõimalustega, näiteks -fprofile-correction või teatud märguannete keelamisega (-Wno-error=coverage-mismatch) faasidevahelisest koodimuutusest tulenevaid sagedasi vigu saab vältida; see on tavaliselt ka kasulik Jälgige jõudlust eBPF-i ja perf-iga täpsete profiilide saamiseks.
Õigesti rakendatuna saab PGO pakuvad palju suuremaid jõudluse parandusi kui lihtsalt taseme tõstmine -OSest see langetab otsuseid reaalsete teostusandmete, mitte üldiste mudelite põhjal. Probleem on aga selles, et see on tülikas protsess: seda tuleb korrata iga olulise koodivärskenduse korral ja see sõltub suuresti sellest, kas testistsenaarium on tegeliku kasutuse representatiivne.
Mõned projektid (sealhulgas GCC ise teatud distributsioonides) pakuvad juba konkreetsed lipud või skriptid PGO automaatseks aktiveerimiseks, kuid üldiselt jääb see tehnikaks edasijõudnutele kasutajatele, kes on valmis protsessi aega investeerima.
Karastamine: lipupõhine turvalisus
Lisaks kiirusele keskenduvad paljud keskkonnad binaarfailide haavatavuste kaitsmisele, isegi jõudluse vähenemise hinnaga. GCC ja kaasaegsed linkerid pakuvad head valikut karastamisvõimalused mida saab aktiveerida CFLAGS/CXXFLAGS ja LDFLAGS kaudu.
Mõned kõige levinumad Heli:
-D_FORTIFY_SOURCE=2o=3lisab teatud libc funktsioonidele täiendavaid kontrolle puhvri ületäitumise tuvastamiseks käitusajal.-D_GLIBCXX_ASSERTIONS: aktiveerib STL-is konteinerite ja C++ stringide piirikontrolli, tuvastades leviulatusest väljas olevaid juurdepääsuvõimalusi.-fstack-protector-strong: lisab pinu kanaarilinnud, et tuvastada kirjutusmärke, mis seda rikuvad.-fstack-clash-protection: leevendab rünnakuid, mis põhinevad pinu ja teiste mälupiirkondade kokkupõrgetel.-fcf-protection: lisab juhtimisvoo kaitset (nt ROP-rünnakute vastu) arhitektuuridele, mis seda toetavad.-fpiekoos-Wl,-pie: genereerib positsioneeritavaid käivitatavaid faile, mis on vajalikud efektiivse ASLR-i jaoks.-Wl,-z,relroy-Wl,-z,nowNeed tugevdavad ümberpaigutustabelit ja keelavad laisa sidumise Sümbolidteatud rünnakuvektorite takistamine.
Mõne distributsiooni "karastatud" profiilidel on paljud neist valikutest juba vaikimisi lubatud. Nende käsitsi aktiveerimine ilma mõju mõistmata võib kaasa tuua märgatavalt aeglasemad binaarfailid., eriti suurtes või väga mälumahukates rakendustes, kuid avatud serverites või tundlikes töölaudades on see tavaliselt mõistliku hinnaga.
Valige kompilaator ja keskkond: GCC, Clang, MSVC, MinGW, Xcode…
Praktikas ei vali sa tihtipeale mitte ainult lippe, vaid ka Millist kompilaatorit ja millist täielikku tööriistaketti kavatsed kasutada? igal platvormil. GCC ja Clang on tavaliselt jõudluselt väga sarnased ning erinevused on märgatavamad diagnostikas, kompileerimisaegades või ühilduvuses teatud laiendustega.
En Windows Teil on mitu marsruuti: Visuaalstuudio (MSVC) koos oma tööriistakomplektidega v143, v142jne; või MinGW-w64 läbi MSYS2 mis annab sulle natiivse Windowsi GCC ja Clangi koos vajalike Win32 teekidega. MSYS2 hallatakse pacman ja pakub MinGW64 keskkondi (klassikalise MSVCRT põhjal) ja UCRT64 (Universal CRT-ga, moodsam).
macOS-is on standardtee Xcode koos clang/clang++-ga, kus võtmekontseptsioon on Baas-SDK (süsteemi versioon, mille jaoks see on kompileeritud) ja Juurutamise sihtmärk (macOS-i minimaalne versioon, millel soovite oma rakendust käivitada). Selle paari õige kohandamine väldib klassikalist katastroofi, kus kompileeritakse ainult uusima süsteemiversiooni jaoks ja teie binaarfailid ei tööta veidi vanemate versioonidega.
Linuxis on tavaline kasutada GCC ja Make või NinjaVõib-olla CMake'i kasutamine metageneraatorina. Lisaks võimaldavad sellised distributsioonid nagu Ubuntu teil installida mitu GCC versiooni ja valida need käsuga update-alternativessarnaselt sellele, kuidas seda macOS-is kasutatakse xcode-select Xcode'ilt lülitumiseks.
Kui vajate Make'i või Ninjaga loodud projektide jaoks mugavaid silumiskeskkondi (mis on ühe konfiguratsiooniga), Eclipse CDT y Visual Studio kood Need on kaks väga käepärast valikut: CMake saab luua vajalikud projektifailid või integreeruda nendega otse konfigureerimiseks, kompileerimiseks ja silumiseks.
Kaasaskantavus ja CMake: sama kood, erinevad tööriistaketid
C/C++ projekti kompileerimiseks ilma koodi puutumata Windowsis, Linuxis ja macOS-is on vaja mõlema head kombinatsiooni. CMake, saadaolevad generaatorid ja erinevad kompilaatoridIdee seisneb selles, et fail CMakeLists.txt Kirjelda projekti abstraktselt ja CMake genereerib igale platvormile sobiva projektitüübi.
Windowsis saab CMake'i käivitada järgmiselt: -G "Visual Studio 17 2022" lahenduse loomiseks msbuildi abil või -G "Ninja" et konsoolist kiiremini ehitada. Lisaks läbi -T v143, v142jne, valite platvormi tööriistakomplekti (MSVC kompilaatori versioon) ja koos -A x64, Win32 o arm64 Sina valid arhitektuuri.
MinGW/MSYS2 puhul on tavaline kasutada järgmist -G "MinGW Makefiles" o -G "Ninja" ja muutujate kaudu CMAKE_C_COMPILER y CMAKE_CXX_COMPILERValige, kas soovite GCC-d või Clangi. Sellisel juhul juhitakse konfiguratsioone (silumine, väljalase jne) kaudu -DCMAKE_BUILD_TYPE, kuna Make ja Ninja on ühe konfiguratsiooniga.
macOS-is -G Xcode See annab sulle ideaalse projekti IDE-s silumiseks ning saad SDK-d ja juurutamise sihtmärki juhtida selliste muutujatega nagu CMAKE_OSX_DEPLOYMENT_TARGETKui tahad ainult Make'i või Ninjat, kasutad samu generaatoreid nagu Linuxis.
Selle kõige ilu seisneb selles, et õigesti seadistatuna saate hallata ühtset koodibaasi ja järjepidevat lippude komplekti (mõnikord platvormispetsiifilisi) ning kompileerida mis tahes keskkonnas ilma, et peaksite pidevalt lähtekoodi kallal nokitsema. Siiski on oluline meeles pidada peamist põhimõtet: Esmalt veenduge, et see töötab korralikult, seejärel kiirendame optimeerimisprotsessi..
Kõike nähtut arvestades on üldine idee jääda selle juurde mõõdukas, kuid tõhus kombinatsioon (midagi sellist) -O2 -march=<cpu adecuada> -pipe pluss mõningane mõistlik karastamine) ja reserveerige suured relvad – LTO, PGO, Graphite, agressiivne OpenMP – nende projektide või moodulite jaoks, mille täiustusi on tõeliselt mõõdetud ning millega kaasnevad hooldus- ja veaotsingukulud on aktsepteeritud.
Kirglik kirjanik baitide maailmast ja üldse tehnoloogiast. Mulle meeldib jagada oma teadmisi kirjutamise kaudu ja just seda ma selles ajaveebis teengi, näitan teile kõike kõige huvitavamat vidinate, tarkvara, riistvara, tehnoloogiliste suundumuste ja muu kohta. Minu eesmärk on aidata teil digimaailmas lihtsal ja meelelahutuslikul viisil navigeerida.