hvordan man tildeler mere vædder til rust


Svar 1:

Kort sagt, stack er enkel og hurtig, men størrelsen er begrænset, og tildelingen varer kun indtil slutningen af ​​funktionen. Heap er meget større, kan bruges til at fordele langsigtet og stort stykke hukommelse og kræver mere styring, så det er ret langsommere at allokere.

Hvorfor er der ikke behov for hukommelsestildeling / deallocation til stack?

Opkaldsstakken, generelt kaldet "stak", er en væsentlig del af funktionsopkaldet. Det bruges til at beholde, godt, stakke variabler og den aktuelle tilstand for opkaldet.

Generelt tildeles den hukommelse, der bruges til stack, med en bestemt bestemt størrelse i begyndelsen af ​​processen / tråden og bruges i hele dens levetid. Derfor siges det, at der ikke er behov for "hukommelsestildeling". Faktisk tæller det også som

en slags hukommelsesallokering

, men tildelingen i stakken er bare for enkel og for hurtig sammenlignet med bunkebaseret tildeling, så vi har tendens til at sige, at der ikke er behov for en "allokering".

Også, hvorfor kan ikke kompilatoren vide, hvor objektet vil være i bunke?

Når du siger dette, har du måske allerede vidst, hvordan stack fungerer. Heap fungerer ikke som en stak. Heap er i det væsentlige en masse hukommelse, som hver del kan tildeles og deallokeres i uspecificeret rækkefølge. Tænk på det som et meget langt tog (men begrænset længde), som hver passager kan besætte et antal sammenhængende bogier. Passagerer klatrer og går af mellem stationer uden nogen kendt plan eller mønstre. Bunkehåndtering er ligesom at styre disse passagerer for at få den mængde bogier, de har brug for, så de kan komme på toget, hvilket kræver sporing af alle tilgængelige bogier og nogle algoritmer for at finde de bedst egnede bogier til dem uden at løbe tør for sammenhængende bogier i det lange løb. Dette er langt mere kompliceret og tidskrævende end at lægge nogle data i opkaldsstakken.

Hvordan kan compileren vide, hvor objektet vil være, hvis det er gemt i stak?

Nye objekter, der hører til opkaldstakken, gemmes altid oven på opkaldstakken. Dette er meget optimeret, fordi stakoperationer er indbygget understøttet i de fleste, hvis ikke alle, CPU-typer, som giver mindst et dedikeret internt register til "stack pointer" - pointer, der peger på toppen af ​​opkaldstakken. Compiler ved faktisk ikke, hvor objektet skal gemmes i stakken, men det kan helt sikkert producere instruktionen til at gøre det.

En stor advarsel er, at selvom vi er sikre på, at stakken er hurtigere end dyng i de fleste tilfælde, taler vi om forskellen i nanosekunder pr. Allokering her. Forsøg ikke at foretage denne form for optimering til enhver triviel funktion.


Svar 2:

Lad os prøve at forklare på almindeligt engelsk, hvad der er stakken og hvad der er bunken.

Stakken er, ja, en stak hukommelsesblokke, der oprettes, når en funktion kaldes. Så hver gang et C ++ - program kalder en funktion (en hvilken som helst funktion. Endda main ()) tildeler det en blok hukommelse på stakken til denne funktion.

Denne hukommelsesblok indeholder alle variabler, der oprettes på denne funktion, samt adressen på den oprindelige funktion, der kaldte den. Når udførelsen af ​​denne funktion slutter, distribuerer den automatisk denne hukommelsesblok og sletter effektivt endda den variable, som funktionen oprettede statisk (enhver int, char, float, uanset hvad). Da kompilatorerne allerede ved, hvor hver enkelt variabel, der oprettes, placeres i hukommelsen, kan den foretage en masse optimeringer.

Medmindre du vil oprette dynamiske variabler. Ting som en markør (eller endda en vektor . I dette tilfælde tildeles elementerne f.eks. Ikke på stakken). Lad os forestille os følgende situation (det er almindeligt C, men fungerer af hensyn til forklaringen):

ugyldig funktion (int m) { int * v = (int *) malloc (m * størrelse af (int)); // gør noget med det}

Hvis du ikke kan læse koden, opretter den simpelthen en række ints med størrelse m ved hjælp af en markør * v. I dette tilfælde har compileren ingen måde at kende størrelsen på arrayet på forhånd, da m kun vil være kendt, når programmet rent faktisk kører. Så hvordan ved compileren, hvor stor hukommelsesblokken til denne funktion i stakken skal være, når den kaldes?

Svaret er: Det gør det ikke. I dette tilfælde (og faktisk i ethvert andet tilfælde, at du bruger pegepinde, selvom det er implicit, som i en std :: vector ), gemmes den aktuelle matrix ikke i stakken, men i bunken, det vil sige en anden region i hukommelsen, uafhængig af stakken. Så tildeler kompilatoren kun nok hukommelse til POINTEREN, ikke dataene (i dette tilfælde sandsynligvis et 32-bit heltal), og selve dataene vil leve i en anden del af hukommelsen med adressen gemt i markøren ( for puristerne omkring er markøren adressen på det første element i arrayet). Problemet med dette er, at når funktionen vender tilbage, har den ikke nogen information om selve arrayet, så det er programmørens pligt at deallocere, og compileren kan bare ikke optimere denne proces så effektivt. Hvis programmøren ikke gør det på den rigtige måde, vil der være en hukommelseslækage.

Den gode del af det er, at da dataene ikke er i stakken, men i bunken, vil de ikke blive ødelagt, når funktionen vender tilbage. Så du kan nemt overføre markøren til data til andre funktioner, der nu kan få adgang til disse data, der lever i bunken.

Al denne samtale er i det væsentlige gyldig for almindelig C. Men underliggende for mange af datastrukturer på C ++ sker det samme. For eksempel tildeler C ++ std :: vector hvert element af vektoren på bunken, ikke på stakken, da det på ingen måde er i stand til at vide på forhånd, hvor mange elementer vektoren kan få i sidste ende.

Der er sandsynligvis et par fejl på min forklaring. Jeg læste det ikke, men hovedideen er denne.

tl; dr: Variabler, der er lokale for en funktion, tildeles på stakken og deles automatisk, når funktionen vender tilbage. Variabler, som compileren ikke på forhånd kan udlede størrelsen på (som vektorer) eller som er markører (som sandsynligvis vil blive brugt af andre funktioner), tildeles på bunken.


Svar 3:

Hovedårsagen er, at stakken passer til den lokale CPU-cache.

Forestil dig at erklære et int i; variabel; sig i en simpel for-loop, såsom for (int i = 0; i

Selv en hardcore Java-person ville indse, at ved at tildele jeg "eksternt" (effektivt ved hjælp af at lave noget Int i = nyt Int (); opkald) spilder du ressourcer.

Sig nu, at du har en klasse, der er et par - eller endda bare et - heltal stort. Hvorfor i alverden vil du tildele det ved hjælp af nyt, hvis der er en nem måde?

Denne nemme måde er mulig - og opmuntres bredt - i C ++, fordi C ++ i stedet for referencetællingsbaseret affaldssamling bruger omfangsbaseret levetidsledelsesstyring. Compileren ved, at når det nuværende omfang slutter, skal objekter, der automatisk tildeles i det, bortskaffes. Således matcher hukommelseslayoutet for lokalt tildelte objekter, hvordan stakken fungerer for indlejrede funktionsopkald.

Denne bekvemmelighed er selvfølgelig ikke en tilfældighed. Det er filosofien om, hvordan C ++ håndterer objektets levetid.

Ulempe: Når der er behov for "fleksibilitet" i objektets levetidsledelse, fungerer almindelige automatisk tildelte objekter ikke - da de er allokeret på stak, vil de ikke længere være gyldige, da den kaldte funktion forlader sit anvendelsesområde. Således skal programmøren eksplicit erklære disse objekter "vedholdende" i større skala - oftest ved hjælp af smarte pekere, selvom dette effektivt er det felt, hvor bare pegepinde og rå nyt / sletning bliver nødvendigt.

(Dette er forresten en almindelig faldgrube i C ++: at sende en markør / henvisning til et stakallokeret objekt i et async-opkald uden at sikre, at den stack-ramme, der er allokeret, vil leve længe nok. Futures og løfter FTW i dette tilfælde. )

Upside: Ydeevne, teknisk skønhed og bemærkelsesværdig forudsigelighed af ledelse af objekt ejerskab.

Hvad selve hukommelsesallokering angår, er det en relativt lille gevinst i ydeevne sammenlignet med datalokalitet for bedre caching. Det er rigtigt, at hukommelsen i sig selv ikke behøver at blive "allokeret" til stack-allokerede objekter, men brugerdefinerede konstruktører og destruktorer til stack-allokerede objekter bliver selvfølgelig kaldt.

Samtidig er hukommelsesfragmentering, det er sådan en smerte for affaldsindsamlede sprog, et problem, der er fuldstændig ikke-eksisterende til stakbaseret allokering og levetidsstyring.

Åh, og nævnte jeg, at denne omfangsbaserede tildeling gør den endelige konstruktion helt unødvendig? Nå, det gør det.


Svar 4:

Det kan være (selvom du i Java ofte ikke har et valg i sagen.)

Her er en simpel begrundelse: tildeling fra stakken handler kun om at justere et register, mens tildeling fra bunken indebærer et funktionsopkald. Selvom funktionsopkaldet er inline, er bunke-datastrukturer ofte ikke-private (i et forsøg på at optimere placeringen af ​​nye objekter for at undgå fragmentering.)

Øvelse: kompilér et C ++ - program ved hjælp af alloca og et ved hjælp af malloc, og se på forskellene i den resulterende kode.

I et affaldsindsamlet system kan det være næsten lige så hurtigt at allokere fra bunken, fordi bunken kan organiseres som en

Regionfordeling

. Men alligevel er der normalt nogle metadata-sporingsobjekter og noget synkronisering, så flere tråde alle kan allokere fra bunken. Tildeling på stakken behøver ingen af ​​disse, fordi vi bare "popper" alt, når funktionen slutter, og hver tråd har allerede sin egen stak.

Derudover kan hukommelsesplacering muligvis fungere til fordel for allokering fra stakken snarere end bunken. Hukommelsen, der bruges til en trådstak, kan muligvis allerede være i CPU'ens L1- eller L2-cache. Det er heller ikke sandsynligt, at der har nogen 'ping pong' -adfærd med en anden CPU, da ingen anden tråd ville have grund til at få adgang til den aktuelle tråds stak.


Svar 5:

Det afhænger af bunkeimplementeringen. Hvis der ikke er nogen bunkehåndtering, så er der ingen forskel, men du vil til sidst løbe tør for bunkeplads, jo mere bruger du den. Jeg har set enkle implementeringer, hvor stakken går fra lave adresser til højere i hukommelsen, og bunken går fra høj til lav, og bunken fortsætter med at vokse, jo mere den bruges, indtil den løber ind i stakken.

Bunkehukommelse holdes adskilt fra stakhukommelse, så de ikke behøver at bekymre sig om at komme rundt om hinanden.

På de fleste moderne systemer styres bunken. Det holder styr på den hukommelse, der ikke er allokeret, og bruger / genbruger den, når der tildeles anmodning. Den mere almindelige implementering behandler den ikke-tildelte bunke som en sammenkædet liste. Baseret på den ønskede hukommelsesstørrelse søger bunken efter ledig plads, der kan rumme den, og markerer den som tildelt. Da søgning på bunken efter ledig plads, der har den rigtige størrelse, er en lineær proces, tager det lidt tid. Det tager mere tid, hvis hukommelsen tildeles og dealloceres meget i små stykker på bunken, fordi dette medfører, at der er flere ledige pladser i bunken at søge igennem på listen. Dette kaldes bunkefragmentering. Der er optimerede strategier til allokering / deallokering af bunkehukommelse, der skal minimere dette, men faktum er, at kun den første bunktildeling vil være så hurtig som en operation på stakken.

Stakken tildeler hukommelse i konstant tid. Tildeling og deallokering af stakplads er enkel. For at skubbe til stakken flytter den bare stakemarkøren forbi stabelrammen og opretter en ny. For at omplacere flytter den stakemarkøren tilbage til begyndelsen af ​​en tidligere stabelramme. Det behøver ikke at bekymre sig om huller i den tildelte hukommelse, som bunken gør.


Svar 6:
Stakken er hurtigere end bunken, fordi stakhukommelse garanteres at frigives i den omvendte rækkefølge, den tildeles. Dette gør det meget nemmere at administrere (f.eks. Ikke nødvendigt at flette gratis områder) og optimerer lokaliteten for hukommelsesadgang.

Svar 7:

Tildeling af stak er meget hurtigere, da alt det virkelig gør er at flytte stakemarkøren. Ved hjælp af hukommelsespuljer kan du få sammenlignelig ydelse ud af bunktildeling, men det kommer med en let ekstra kompleksitet og dens egen hovedpine. Også, stack vs heap er ikke kun en ydeevne overvejelse; det fortæller dig også meget om objekternes forventede levetid. Stakkens størrelse er endelig, da du hurtigt finder ud af, om du bruger for meget stakallokering. Stackallokering foretrækkes til langvarige serverapplikationer. Selv de bedst administrerede dynger bliver til sidst så fragmenterede, at applikationsydelsen forringes.


Svar 8:

De fleste af svarene er for fanget i abstraktionerne.

Stakallokering udføres på CPU-niveau ved hjælp af en simpel subindføring i stakemarkøren.

På den anden side administreres bunketildeling af operativsystemet eller en anden abstraktion, der “styrer” hukommelse.

Hvad er hurtigere - en enkelt CPU-instruktion eller det sæt af operationer, der udføres ved at kalde et operativsystems hukommelsesstyringsalgoritme? ;)

Heapallokeringer udføres på kørselstid, så der er ingen måde for en compiler at vide, hvor i bunken noget bliver allokeret.


Svar 9:

Staklayoutet er opsat på kompileringstidspunktet, bunken ved kørselstidspunktet. Så den grundlæggende tildeling er noget hurtigere i stakken, men den tid er normalt dværget af de andre trin i objektkonstruktion, som at køre konstruktøren.