Miks on esimene C++ (m)jaotus alati 72 KB? | Mewayz Blog
Hacker News

Miks on esimene C++ (m)jaotus alati 72 KB?

Kommentaarid

9 min read Via joelsiks.com

Mewayz Team

Editorial Team

Hacker News

Müsteerium teie esimese C++ eraldamise taga

Te kirjutate lihtsa C++ programmi. Üksik uus int. Neli baiti. Käivitate strace või oma lemmikmäluprofiili ja seal see on – teie protsess nõudis just operatsioonisüsteemilt ligikaudu 72 KB. Mitte 4 baiti. Mitte 64 baiti. Täielik 72 KB. Kui olete kunagi seda numbrit vahtinud ja mõelnud, kas teie tööriistad valetavad teile, pole te üksi. See pealtnäha veider käitumine on üks korduma kippuvaid küsimusi C++ arendajate seas, kes esimest korda mälu sisemusi uurivad, ja vastus viib meid põnevale teekonnale läbi kihtide, mis asuvad teie koodi ja tegeliku riistvara vahel.

Mis juhtub, kui helistate uuele

72 KB suuruse mõistmiseks peate jälgima kogu jaotusahelat. Kui teie C++ kood käivitab new int, muudab kompilaator selle väljakutseks operaatorile new, mis enamikus Linuxi süsteemides delegeerib glibc-st malloc. Kuid malloc ei küsi otse kernelilt 4 baiti mälu. Kernel töötab lehtedena – tavaliselt x86_64 puhul 4 KB – ja süsteemikõne hind on võrreldes lihtsa mälujuurdepääsuga tohutu. brk() või mmap() kutsumine iga üksiku jaotuse jaoks paneks iga mittetriviaalse programmi seiskuma.

Selle asemel toimib vahendajana glibc-i mälujaotur – rakendus nimega ptmalloc2, mis ise põlvneb Doug Lea klassikalisest dlmallocist. See nõuab tuumalt eelnevalt suuri mäluplokke ja lõikab need seejärel väiksemateks tükkideks, kui teie programm neid vajab. See on peamine põhjus, miks teie esimene 4-baidine eraldamine käivitab operatsioonisüsteemile palju suurema taotluse. Jaotaja ei ole raiskav. See on strateegiline.

72 KB lahkamine: kuhu lähevad baidid

Esialgne jaotamise üldkulu pärineb mitmest erinevast komponendist, mille käitusaeg peab lähtestama, enne kui see suudab teile anda kasvõi ühe baidi kasutatavat mälu. Iga komponendi mõistmine selgitab, miks arv langeb sinna, kuhu see jõuab.

Esiteks initsialiseerib glibc malloc peamise areeni – peamise raamatupidamisstruktuuri, mis jälgib kõiki põhilõime allokatsioone. See areen sisaldab kuhja metaandmeid, vabaloendi viiteid ja prügikasti struktuure erinevate jaotussuuruste jaoks. Jaotaja pikendab programmi pausi läbi sbrk() ja esialgset laiendust juhib sisemine parameeter nimega M_TOP_PAD, mis vaikimisi on 128 KB täidis. Tegelikku esialgset taotlust kohandatakse aga vastavalt lehe joondusele ja olemasolevale katkestuse positsioonile, mille tulemuseks on sageli väiksem esimene taotlus – tavaliselt maandub see äsja alustatud protsessis selle 72 KB suuruse lähedale.

Teiseks, alates glibc versioonist 2.26 lähtestab jaotaja esmakordsel kasutamisel lõime kohaliku vahemälu (tcache). Vahemälu sisaldab 64 salve (üks väikese jaotuse suurusklassi kohta), millest igaüks mahutab kuni 7 vahemällu salvestatud tükki. tcache_perthread_struct ise tarbib umbes 1 KB, kuid selle lähtestamine käivitab laiema areeni seadistamise. Kolmandaks on C++ käituskeskkond juba jaotanud enne, kui teie main() isegi käivitub – staatilised konstruktorid, iostream puhvri lähtestamine std::cout ja sõprade jaoks ning lokaadi seadistus aitavad kaasa sellele esialgsele hunniku jalajäljele.

Areenisüsteem ja miks on eeljaotamine nutikas

Otsus eraldada märkimisväärne osa mälust, mitte seda osade kaupa taotleda, ei ole rakendamise juhus. See on tahtlik tehniline kompromiss, mis põhineb aastakümnete pikkusel süsteemide programmeerimise kogemusel. Iga kõne brk() või mmap() hõlmab konteksti lülitumist kasutajaruumist kerneli ruumi, protsessi virtuaalse mälu vastendamise muutmist ja võimalikke lehetabeli värskendusi. Kaasaegse riistvara puhul maksab üks süsteemikõne ligikaudu 100–200 nanosekundit – eraldiseisvalt tühine, mastaabis katastroofiline.

Kaaluge programmi, mis teeb lähtestamise ajal 10 000 väikest jaotust. Ilma eeljaotuseta tähendaks see 10 000 süsteemikõnet, mis maksavad umbes 1–2 millisekundit puhast üldkulusid. Areenil põhineva jaoturi puhul käivitab esimene jaotus ühe süsteemikutse ja järgnevaid 9999 jaotust teenindatakse täielikult kasutajaruumis kursori aritmeetika ja lingitud loendi toimingute kaudu – igaüks võtab aega ligikaudu 10–50 nanosekundit. Matemaatika on ühemõtteline: eeljaotus võidab suurusjärkude kaupa.

72 KB, mida näete oma esimesel jaotamisel, ei ole raisatud mälu – see on jõudlusinvesteering. Jaotaja panustab, et teie programm teeb varsti rohkem eraldusi ja peaaegu iga reaalse maailma stsenaariumi korral tasub see panus end kuhjaga ära. Kasutamata virtuaalse aadressiruumi hind on tänapäevastes 64-bitistes süsteemides sisuliselt null.

Virtuaalne mälu vs. füüsiline mälu: miks see pole oluline

Sellega esimest korda kokku puutuvate arendajate seas on levinud murekoht ressursside raiskamine. Kui mul on vaja ainult 4 baiti, siis miks mu programm tarbib 72 KB? Kriitiline arusaam on, et virtuaalmälu ei ole füüsiline mälu. Kui glibc pikendab programmi pausi 72 KB võrra, värskendab kernel protsessi virtuaalse mälu vastendusi, kuid see ei varusta neid lehti kohe füüsilise RAM-iga. Tegelikud füüsilised lehed eraldatakse nõudmisel lehetõrgete kaudu – ainult siis, kui teie programm kirjutab kindlale aadressile, määrab tuum sellele tõelise mälulehe.

💡 DID YOU KNOW?

Mewayz replaces 8+ business tools in one platform

CRM · Invoicing · HR · Projects · Booking · eCommerce · POS · Analytics. Free forever plan available.

Start Free →

See tähendab, et kuigi teie protsessi virtuaalne maht suureneb 72 KB võrra, suureneb selle residentse komplekti suurus (RSS) – tegelikult kulutatud füüsilise RAM-i hulk – ainult nende lehtede võrra, mida te tegelikult puudutate. Üksiku uue int puhul on see tavaliselt üks 4 KB leht, millele lisanduvad kõik lehed, mida areeni metaandmed hõivavad. Ülejäänud virtuaalne ruum asub seal ja on kasutamiseks valmis. See ei maksa midagi peale aadressiruumi – millest teil on 64-bitises Linuxi süsteemis 128 TB.

See eristamine on tootmisrakenduste profiilide koostamisel ja jälgimisel kriitiline. Kui loote tarkvara, mis peab jälgima tegelikku ressursitarbimist – olgu see siis SaaS-i taustaprogramm, mikroteenus või analüütikakonveier, nagu need, mis töötavad äritegevuse jaoks sellistel platvormidel nagu Mewayz –, peaksite alati jälgima RSS-i, mitte virtuaalset suurust. Sellised tööriistad nagu /proc/[pid]/smaps, valgrind --tool=massif ja pmap võivad anda teile täpseid füüsilise mälu jalajälgi, mitte eksitavaid virtuaalmälu näitajaid.

Kuidas erinevad jaotajad esimest jaotust käsitlevad

72 KB arv on spetsiifiline glibc ptmalloc2 jaoks. Teised jaotajad teevad erinevaid kompromisse ja esialgne jaotamise üldkulu varieerub vastavalt. Nende erinevuste mõistmine on kasulik jõudlustundlike rakenduste jaoks jaoturi valimisel.

  • jemalloc (kasutab Facebook, FreeBSD) – kasutab täpsemat areenistruktuuri koos lõimede lokaalsete vahemäludega. Esialgne üldkulu kipub olema suurem (sageli 200+ KB), kuid see tagab parema mitmelõimelise jõudluse tänu väiksemale lukustusele.
  • tcmalloc (Google'i lõime vahemällu salvestav Malloc) – eraldab vaikimisi umbes 2 MB vahemälu lõime kohta agressiivse eeleraldisega. Esialgne üldkulu on suurem, kuid järgnevad väikesed eraldised on äärmiselt kiired.
  • musl libc malloc – kasutab kõigi jaotuste jaoks palju lihtsamat kujundust, mis põhineb mmapil. Esialgne üldkulu on minimaalne (sageli vaid 4 KB ühe jaotuse kohta), kuid jaotuse hind on suurem tänu sagedasematele süsteemikõnedele.
  • mimalloc (Microsoft) – kasutab segmendipõhist jaotamist 64 MB segmentidega. Esimene jaotus käivitab 64 MB virtuaalse broneeringu (minimaalse füüsilise kohustusega), kaupleb aadressiruumi erakordse asukoha ja läbilaskevõime jaoks.

Valik nende jaoturite vahel sõltub täielikult teie töökoormusest. Pikaajaliste serverirakenduste puhul, millel on raske mitmelõimeline jaotamine, ületab jemalloc või tcmalloc tavaliselt glibc vaikeväärtust. Mälupiiranguga manussüsteemide puhul võib vaatamata väiksemale läbilaskevõimele eelistada musli lihtsamat lähenemist. Enamiku üldotstarbeliste töölaua- ja serverirakenduste jaoks on ptmalloc2 72 KB esialgne üldkulu mõistlik vaikeväärtus, mis töötab hästi ilma häälestamiseta.

Esialgse eraldamise käitumise häälestamine

Kui algne vaikeväärtus 72 KB on teie kasutusjuhtumi jaoks tõeliselt problemaatiline – võib-olla sünnite tuhandeid lühiajalisi protsesse, millest igaüks eraldab vaid käputäie –, pakub glibc mitu häälestatavat funktsiooni mallopt() ja keskkonnamuutujate perekonna MALLOC_ kaudu.

Parameeter M_TOP_PAD juhib seda, kui palju lisamälu jaotur nõuab peale selle, mida kohe vaja on. Kui määrate selle väärtuseks mallopt(M_TOP_PAD, 0), kästakse jaoturil taotleda ainult seda, mis on vajalik, vähendades oluliselt esialgset üldkulusid. Parameeter M_MMAP_THRESHOLD juhib suurust, millest suuremad jaotused kasutavad areeni asemel mmapi. Nupp M_TRIM_THRESHOLD juhib seda, millal vabastatud mälu OS-i tagastatakse. Ja kuna glibc 2.26, võimaldavad häälestused glibc.malloc.tcache_count ja glibc.malloc.tcache_max teil juhtida lõime vahemälu käitumist.

Siiski, hoiatus: nende parameetrite häälestamine ilma hoolika võrdlusuuringuta muudab asja peaaegu alati hullemaks. Vaikeseaded valiti ulatusliku reaalmaailma profiilide põhjal ja need on enamiku töökoormuste jaoks meeldivad. Kui teil pole tootmisprofiilide põhjal kindlaid tõendeid selle kohta, et malloc üldkulud on kitsaskoht – ja olete mõõtnud oma muudatuste mõju – jätke vaikeseaded rahule. Jaoturi enneaegne optimeerimine on jaki raseerimise eriti salakaval vorm, mis on kulutanud lugematuid tehnilisi tunde, andes tühise kasu.

Mida see meile süsteemide programmeerimise kohta õpetab

72 KB esmase eraldamise müsteerium on oma põhiolemuselt õppetund abstraktsioonikihtide kohta. C++ loob teile illusiooni, et uus int eraldab 4 baiti. Keelestandard ütleb nii. Sinu vaimne mudel ütleb nii. Kuid teie koodi ja riistvara vahel on hunnik keerukaid süsteeme – C++ käitusaeg, C-teegi jaotur, kerneli virtuaalmälu alamsüsteem ning riistvara MMU ja TLB –, millest igaüks lisab oma käitumisviisid, optimeerimised ja üldkulud.

See ei ole viga. See on kogu süsteemitarkvara mõte. Iga kiht eksisteerib tõelise probleemi lahendamiseks: jaotaja on olemas, nii et te ei pea iga jaotuse jaoks süsteemikutseid tegema. Virtuaalse mälu süsteem on olemas, nii et te ei pea otse füüsilist mälu haldama. Lehe tõrkekäsitleja on olemas, nii et mälu kasutatakse laisalt ja tõhusalt. Iga kiht vahetab väikese koguse läbipaistvust suure jõudluse ja mugavuse vastu.

Kõige usaldusväärsemaid ja kõige tõhusamaid süsteeme loovad arendajad, kes mõistavad neid kihte – mitte sellepärast, et neil on vaja neile pidevalt mõelda, vaid seetõttu, et kui juhtub midagi ootamatut (nt salapärane 72 KB jaotus), on neil mõttemudel põhjuse mõistmiseks. Olenemata sellest, kas ehitate reaalajas kauplemissüsteemi, mängumootorit või tuhandeid kasutajaid teenindavat äriplatvormi, eristab kompetentsed arendajad erandlikest arendajatest võime mõelda, mida teie kood tegelikult süsteemi tasemel teeb. 72 KB pole viga. See on teie jaotaja, kes teeb oma tööd suurepäraselt.

Ehitage oma ettevõtte operatsioonisüsteem juba täna

Vabakutselistest agentuurideni – Mewayz pakub 207 integreeritud mooduliga 138 000+ ettevõtet. Alustage tasuta, uuendage, kui kasvate.

Loo tasuta konto →

Try Mewayz Free

All-in-one platform for CRM, invoicing, projects, HR & more. No credit card required.

Start managing your business smarter today

Join 30,000+ businesses. Free forever plan · No credit card required.

Ready to put this into practice?

Join 30,000+ businesses using Mewayz. Free forever plan — no credit card required.

Start Free Trial →

Ready to take action?

Start your free Mewayz trial today

All-in-one business platform. No credit card required.

Start Free →

14-day free trial · No credit card · Cancel anytime