Hacker News

Per què la primera assignació de C++ (m) sempre és de 72 KB?

Comentaris

11 min read Via joelsiks.com

Mewayz Team

Editorial Team

Hacker News

El misteri darrere de la vostra primera assignació de C++

Escriu un programa C++ senzill. Un sol nou int. Quatre bytes. Activeu strace o el vostre perfilador de memòria preferit, i aquí està: el vostre procés acaba de sol·licitar aproximadament 72 KB al sistema operatiu. No 4 bytes. No 64 bytes. 72 KB complets. Si alguna vegada us heu mirat aquest número i us heu preguntat si les vostres eines us mentien, no esteu sols. Aquest comportament aparentment estrany és una de les preguntes més freqüents entre els desenvolupadors de C++ que estudien els elements interns de la memòria per primera vegada, i la resposta ens porta a un viatge fascinant per les capes que hi ha entre el codi i el maquinari real.

Què passa quan truqueu a nou

Per entendre la xifra de 72 KB, cal traçar la cadena d'assignació completa. Quan el vostre codi C++ executa new int, el compilador ho tradueix en una crida a operator new, que a la majoria de sistemes Linux delega a malloc des de la glibc. Però malloc no demana directament al nucli 4 bytes de memòria. El nucli funciona en pàgines (normalment 4 KB a x86_64) i el cost d'una trucada al sistema és enorme en relació amb un simple accés a la memòria. La crida a brk() o mmap() per a cada assignació individual aturaria qualsevol programa no trivial.

En canvi, l'assignador de memòria de glibc, una implementació anomenada ptmalloc2, descendent del clàssic dlmalloc de Doug Lea, actua com a intermediari. Sol·licita grans blocs de memòria del nucli per endavant, i després els talla en peces més petites a mesura que el vostre programa els necessita. Aquesta és la raó fonamental per la qual la vostra primera assignació de 4 bytes desencadena una sol·licitud molt més gran al sistema operatiu. L'assignador no està fent malbaratament. Està sent estratègic.

Dissecció dels 72 KB: on van els bytes

La sobrecàrrega d'assignació inicial prové de diversos components diferents que el temps d'execució ha d'inicialitzar abans que us pugui lliurar fins i tot un únic byte de memòria utilitzable. Entendre cada component s'explica per què el nombre aterra on ho fa.

Primer, el malloc de glibc inicialitza l'escenari principal: l'estructura de comptabilitat principal que fa un seguiment de totes les assignacions al fil principal. Aquest camp inclou metadades per a la pila, punters de llista lliure i estructures de safata per a diferents mides d'assignació. L'assignador amplia l'interrupció del programa mitjançant sbrk(), i l'extensió inicial es regeix per un paràmetre intern anomenat M_TOP_PAD, que per defecte és de 128 KB de farciment. Tanmateix, la sol·licitud inicial real s'ajusta per a l'alineació de la pàgina i la posició de ruptura existent, la qual cosa sovint dóna lloc a una primera sol·licitud més petita, que normalment arriba a prop d'aquesta xifra de 72 KB en un procés acabat de començar.

En segon lloc, des de la glibc 2.26, l'assignador inicialitza una caché local de fils (tcache) en el primer ús. El tcache conté 64 contenidors (un per classe de mida d'assignació petita), cadascun d'ells capaç de contenir fins a 7 fragments en memòria cau. El mateix tcache_perthread_struct consumeix al voltant d'1 KB, però l'acte d'inicialitzar-lo activa la configuració de l'arena més àmplia. En tercer lloc, el temps d'execució de C++ ja ha realitzat assignacions abans que el vostre main() fins i tot s'executi: els constructors estàtics, la inicialització de la memòria intermèdia iostream per a std::cout i la configuració de la configuració regional contribueixen a aquesta petjada inicial del munt.

El sistema Arena i per què l'assignació prèvia és intel·ligent

La decisió d'assignar prèviament una part substancial de la memòria en lloc de sol·licitar-la a poc a poc no és un accident d'implementació. És un compromís d'enginyeria deliberat arrelat en dècades d'experiència en programació de sistemes. Cada crida a brk() o mmap() implica un canvi de context de l'espai d'usuari a l'espai del nucli, la modificació de les assignacions de memòria virtual del procés i actualitzacions potencials de la taula de pàgines. Al maquinari modern, una única trucada al sistema costa aproximadament entre 100 i 200 nanosegons, trivial aïlladament, catastròfic a escala.

Penseu en un programa que fa 10.000 petites assignacions durant la inicialització. Sense l'assignació prèvia, això significaria 10.000 trucades al sistema, amb un cost aproximat d'1 a 2 mil·lisegons de sobrecàrrega pura. Amb un assignador basat en l'arena, la primera assignació desencadena una única trucada al sistema i les 9.999 assignacions posteriors es atenen completament a l'espai de l'usuari mitjançant aritmètica de punters i operacions de llista enllaçada, cadascuna pren uns 10-50 nanosegons. Les matemàtiques són inequívoques: la preassignació guanya per ordres de magnitud.

Els 72 KB que veieu a la vostra primera assignació no són memòria malgastada; és una inversió de rendiment. L'assignador aposta que el vostre programa farà més assignacions aviat, i en pràcticament tots els escenaris del món real, aquesta aposta val molt bé. El cost de l'espai d'adreces virtuals no utilitzat és bàsicament zero als sistemes moderns de 64 bits.

Memòria virtual versus memòria física: per què no importa

Una preocupació comuna entre els desenvolupadors que troben aquest comportament per primera vegada és el malbaratament de recursos. Si només necessito 4 bytes, per què el meu programa consumeix 72 KB? La idea crítica és que la memòria virtual no és memòria física. Quan la glibc amplia l'interrupció del programa en 72 KB, el nucli actualitza els mapes de memòria virtual del procés, però no torna immediatament enrere aquestes pàgines amb RAM física. Les pàgines físiques reals s'assignen sota demanda mitjançant errors de pàgina; només quan el vostre programa escriu a una adreça específica, el nucli li assigna una pàgina real de memòria.

💡 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 →

Això vol dir que tot i que la mida virtual del vostre procés augmenta en 72 KB, la seva mida del conjunt resident (RSS), la quantitat de RAM física que es consumeix realment, augmenta només les pàgines que toqueu realment. Per a un sol new int, normalment és una pàgina de 4 KB, més les pàgines que ocupin les metadades de l'arena. L'espai virtual restant es troba allà, llest per al seu ús, i no costa més que espai d'adreces, dels quals teniu 128 TB en un sistema Linux de 64 bits.

Aquesta distinció és fonamental a l'hora de crear perfils i supervisar les aplicacions de producció. Si esteu creant programari que necessita fer un seguiment del consum de recursos reals, ja sigui un backend SaaS, un microservei o un pipeline d'anàlisi com els que s'executen en plataformes com Mewayz per a operacions empresarials, sempre hauríeu de supervisar RSS en lloc de la mida virtual. Eines com /proc/[pid]/smaps, valgrind --tool=massif i pmap us poden oferir petjades precises de la memòria física en lloc d'enganyar les xifres de memòria virtual.

Com els diferents assignadors gestionen la primera assignació

La xifra de 72 KB és específica del ptmalloc2 de glibc. Altres distribuïdors fan diferents compensacions i la sobrecàrrega d'assignació inicial varia en conseqüència. Entendre aquestes diferències és valuós a l'hora de triar un assignador per a aplicacions sensibles al rendiment.

  • jemalloc (utilitzat per Facebook, FreeBSD): utilitza una estructura d'arena més granular amb memòria cau local de fils. La sobrecàrrega inicial acostuma a ser més alta (sovint més de 200 KB), però ofereix un millor rendiment multiprocés a causa de la reducció de la contenció de bloqueig.
  • tcmalloc (Tread-Caching Malloc de Google): assigna una memòria cau per fil d'aproximadament 2 MB de manera predeterminada, amb una preassignació agressiva. La sobrecàrrega inicial és més alta, però les petites assignacions posteriors són extremadament ràpides.
  • malloc de musl libc — Utilitza un disseny molt més senzill basat en mmap per a totes les assignacions. La sobrecàrrega inicial és mínima (sovint només 4 KB per assignació), però el cost per assignació és més elevat a causa de les trucades al sistema més freqüents.
  • mimalloc (Microsoft): utilitza l'assignació basada en segments amb segments de 64 MB. La primera assignació activa una reserva virtual de 64 MB (amb un compromís físic mínim), espai d'adreces comercials per a una localitat i un rendiment excepcionals.

L'elecció entre aquests distribuïdors depèn completament de la vostra càrrega de treball. Per a aplicacions de servidor de llarga execució amb una gran assignació de múltiples fils, jemalloc o tcmalloc solen superar el rendiment predeterminat de glibc. Per als sistemes incrustats amb restriccions de memòria, l'enfocament més senzill de musl pot ser preferible malgrat el menor rendiment. Per a la majoria d'aplicacions d'escriptori i de servidor de propòsit general, la sobrecàrrega inicial de 72 KB de ptmalloc2 representa un valor predeterminat raonable que funciona bé sense ajustar.

Ajustar el comportament de l'assignació inicial

Si la sobrecàrrega inicial de 72 KB per defecte és realment problemàtica per al vostre cas d'ús (potser esteu generant milers de processos de curta durada, cadascun fent només un grapat d'assignacions), la glibc proporciona diversos ajustables mitjançant mallopt() i la família de variables d'entorn MALLOC_

.

El paràmetre M_TOP_PAD controla la quantitat de memòria addicional que sol·licita l'assignador més enllà de la que es necessita immediatament. Establir-lo a 0 amb mallopt(M_TOP_PAD, 0) indica a l'assignador que sol·liciti només el que es necessita, reduint significativament la sobrecàrrega inicial. El paràmetre M_MMAP_THRESHOLD controla la mida per sobre de la qual les assignacions utilitzen mmap en lloc de l'arena. M_TRIM_THRESHOLD controla quan es retorna la memòria alliberada al sistema operatiu. I des de la glibc 2.26, els ajustables glibc.malloc.tcache_count i glibc.malloc.tcache_max us permeten controlar el comportament de la memòria cau del fil.

Tanmateix, una paraula de precaució: ajustar aquests paràmetres sense una anàlisi comparativa acurada gairebé sempre empitjora les coses. Els valors predeterminats es van triar en funció d'un ampli perfil del món real i representen un punt dolç per a la gran majoria de les càrregues de treball. A menys que tingueu proves sòlides del perfil de producció que la sobrecàrrega de malloc és un coll d'ampolla, i hàgiu mesurat l'impacte dels vostres canvis, deixeu els valors predeterminats. L'optimització prematura de l'assignador és una forma particularment insidiosa d'afaitar iac que ha consumit innombrables hores d'enginyeria per obtenir un benefici insignificant.

Què ens ensenya això sobre la programació de sistemes

El misteri de la primera assignació de 72 KB és, en el seu nucli, una lliçó sobre les capes d'abstracció. C++ us dóna la il·lusió que new int assigna 4 bytes. La norma lingüística així ho diu. El teu model mental ho diu. Però entre el vostre codi i el maquinari hi ha una pila de sistemes sofisticats: el temps d'execució C++, l'assignador de biblioteques C, el subsistema de memòria virtual del nucli i la MMU i TLB del maquinari, cadascun afegint els seus propis comportaments, optimitzacions i despeses generals.

Això no és cap defecte. És el punt complet del programari de sistemes. Cada capa existeix per resoldre un problema real: l'assignador existeix perquè no hagis de fer trucades al sistema per a cada assignació. El sistema de memòria virtual existeix, de manera que no cal gestionar directament la memòria física. El gestor d'errors de pàgina existeix, de manera que la memòria es compromet de manera mandrosa i eficient. Cada capa intercanvia una petita quantitat de transparència per una gran quantitat de rendiment i comoditat.

Els desenvolupadors que creen els sistemes més fiables i de rendiment més alt són els que entenen aquestes capes, no perquè hagin de pensar-hi constantment, sinó perquè quan passa alguna cosa inesperada (com ara una misteriosa assignació de 72 KB), tenen el model mental per entendre per què. Tant si esteu construint un sistema de comerç en temps real, un motor de jocs o una plataforma empresarial que serveixi a milers d'usuaris, la capacitat de raonar sobre què fa realment el vostre codi a nivell de sistema és el que separa els desenvolupadors competents dels excepcionals. Els 72 KB no són un error. És el vostre repartidor fent la seva feina de manera brillant.

Creeu el vostre sistema operatiu empresarial avui mateix

Des d'autònoms fins a agències, Mewayz impulsa més de 138.000 empreses amb 207 mòduls integrats. Comença gratis, actualitza quan creixis.

Crea un compte gratuït →

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