Gå til innhold

Korleis skrive C i 2016


Anbefalte innlegg

Videoannonse
Annonse

Trur dei fleste som programmerer C veit godt kva -march=native gjer. Eg brukar det heile tida.
Når det gjelder calloc vs malloc, så veit eg for lite om forskjellane. Tipper begge har fordeler og ulemper i edge cases, men i lengden så forstår eg visstnok calloc er tryggare.

Lenke til kommentar

Men gir det mening å si at man eksklusivt skal bruke calloc?

 

Angående native så er det ingenting galt i å bruke det. Det blir bare for dumt å anbefale native som en rule of thumb når du fullstendig bryter med kompatibilitet på tvers av prosessorer, spesielt når man anbefaler -O2 over -O3 samtidig.

Lenke til kommentar

Men gir det mening å si at man eksklusivt skal bruke calloc?

Honestly, ikke egentlig gitt at man har høvelig kontroll. Det hindrer at man leser minne som ser fornuftig ut men som bare er garbage. Det gjør også at man kan teste for 0 som ikke-assignet, -gitt- at dette ikke er faktisk god data. Det holder ikke nødvendigvis, som i tilfellet array-of-integers [0,N).

 

calloc har nesten ikke performance impact, og interfacet er på noen måter bedre. Sånn sett gir det mening, men veldig mange programmere kan finne på å forvente malloc for enkelte typer data, og ettersom minne typisk er on-demand i linux kan det fint gi mening å malloc'e en -svær- (logisk) block og fylle underveis, fremfor å skrive til hele blocken med en gang. Dette gjør at man kan over-committe og ikke få problemer før maskinen faktisk er tom og annet. Hvorvidt dette er positivt eller ikke er sikkert greit å diskutere, og jeg har ikke noe godt, entydig svar på det, men jeg vet ikke om jeg synes calloc-only er en bra regel. malloc er ikke problematisk som følge av mangel på init. Ikke mer enn C er i utgangspunktet.

Lenke til kommentar

C99 allows variable declarations anywhere

Etter å ha brukt språk der dette er mulig kan jeg trygt si at som hovedregel er dette ikke måten å gjøre det på. En liste med meningsbærende variabelnavn øverst i hver funksjon gjør koden betydelig enklere å navigere.

 

C99 allows variable length array initializers

Vil ikke dette tvinge kompilatoren til å lage en eksplisitt stack frame? Dermed bruker man opp et ekstra register (= potensielt dårligere ytelse) og må bevare dette registeret ved hvert funksjonskall (dårligere ytelse). Selvsagt vinner man også noe på å unngå malloc.

Lenke til kommentar

Etter å ha brukt språk der dette er mulig kan jeg trygt si at som hovedregel er dette ikke måten å gjøre det på. En liste med meningsbærende variabelnavn øverst i hver funksjon gjør koden betydelig enklere å navigere.

Nei. Lokalitet av navn er langt overlegent, og jo mer scope-begrenset et navn er jo bedre. På den måten hindrer man at informasjon lekker, man gjør logikk mer konsentrert og man kan utelukke at data blir manipulert på annet hold.

 

 

 

Vil ikke dette tvinge kompilatoren til å lage en eksplisitt stack frame?

C-standarden sier ingenting om stack (selv om jeg ikke vet om noen (populær) implementasjon som ikke bruker stack. Det er plenty en kan gjøre uten at dette er problematisk uansett.

 

 

Dermed bruker man opp et ekstra register (= potensielt dårligere ytelse) og må bevare dette registeret ved hvert funksjonskall (dårligere ytelse). Selvsagt vinner man også noe på å unngå malloc.

Man trenger kun ekstra data de gangene det brukes, så det er uansett en lokal greie.

Endret av Lycantrophe
Lenke til kommentar

 

 

jo mer scope-begrenset et navn er jo bedre.
Enig, men jeg mener at navnene bør deklareres øverst i scopet, ikke hist og pist.

 

Angående det siste, tror jeg ikke du forsto hva jeg mente. Alle kompilatorer bruker selvsagt stakken, men de kan lage eksplisitte (lokale variable adresseres relativt til ebp) eller implisitte (ditto til esp) stack frames. Implisitte er i seg selv raskere, og frigjør også ebp-registeret til andre ting. Exception handling krever eksplisitte stack frames. Umiddelbart klarer jeg ikke å se noen måte å kompilere void *array[arrayLength]; på uten å bruke eksplitt stack frame.

 

Dette fører til at ved denne koden:

 

int foo(arrayLength) {
    void *array[arrayLength];
    for (int i=0; i<50000000; ++i) {
        // push ebp
        bar(array, arrayLength);
        // pop ebp
    }
}

Så får man 100000000 unødvendige push og pop (markert). Med mindre kompilatoren klarer å trylle dem bort.

Lenke til kommentar

Enig, men jeg mener at navnene bør deklareres øverst i scopet, ikke hist og pist.

Det er alternativer til "øverst i scopet" og "hist og pist"

 

Angående det siste, tror jeg ikke du forsto hva jeg mente. Alle kompilatorer bruker selvsagt stakken,

Plenty av ikke-stack-baserte implementasjoner, så dette er en urimelig antagelse.

 

men de kan lage eksplisitte (lokale variable adresseres relativt til ebp) eller implisitte (ditto til esp) stack frames.

what.

 

Implisitte er i seg selv raskere, og frigjør også ebp-registeret til andre ting. Exception handling krever eksplisitte stack frames. Umiddelbart klarer jeg ikke å se noen måte å kompilere void *array[arrayLength]; på uten å bruke eksplitt stack frame.

Altså, array-størrelse er nå gitt av runtime, men forandrer seg ikke innenfor et activation record. Når man evaluerer framen jobber man frem til der man kan gjøre allokeringen og oppdaterer størrelsen på alle variabler der. Minimal ekstra jobb utover det å sette opp activation records i det hele tatt.

 

Dette fører til at ved denne koden:

 

int foo(arrayLength) {
    void *array[arrayLength];
    for (int i=0; i<50000000; ++i) {
        // push ebp
        bar(array, arrayLength);
        // pop ebp
    }
}
Så får man 100000000 unødvendige push og pop (markert). Med mindre kompilatoren klarer å trylle dem bort.

 

Nei, se over.
Lenke til kommentar
implsitte og eksplisitte variabler

Implisitte og eksplitte stack frames, ikke variabler. Dette er to forskjellige måter å implementere konseptet (om du ønsker å skille mellom konkret og konsept) activation records på.

 

En implisitt stack frame er en implementasjon som ikke bruker noen andre "variabler" enn registeret esp til å implementere en activation record. Dette er det du får når du bruker -fomit-frame-pointer i gcc.

 

En eksplisitt stack frame er en implementasjon som motsatt bruker andre "variabler" i tillegg til registeret esp (og disse andre "variablene" er praktisk talt alltid ebp (eller rbp i 64-bit)).

 

Ved første øyekast vil allokering av dynamiske arrays på stacken kreve enten bruk av eksplisitt stack frame eller run-time patching av koden (hver gang man går inn i funksjonen) for aksessering av lokale variabler.

Endret av Emancipate
Lenke til kommentar

Ok, det ser ut som om det ikke blir noe ekstra push/pop:

 

Eksempel:

int foo(int arrayLength) {
    void *array[arrayLength];
    for (int i=0; i<50000000; ++i) {
        bar(array);
    }
}

int main(int argc, char **argv) {
    foo(argc);
}

Kompiler med:

$ gcc testdynamic.c -std=c99 -S -masm=intel -O3 -fverbose-asm
 

Bytt ut arrayLength med 10 for å se forskjellen i generert kode.

Endret av Emancipate
Lenke til kommentar

 

implsitte og eksplisitte variabler

Implisitte og eksplitte stack frames, ikke variabler. Dette er to forskjellige måter å implementere konseptet (om du ønsker å skille mellom konkret og konsept) activation records på.

 

Ah. Det er uproblematisk.

 

En implisitt stack frame er en implementasjon som ikke bruker noen andre "variabler" enn registeret esp til å implementere en activation record. Dette er det du får når du bruker -fno-omit-frame-pointer i gcc.

 

En eksplisitt stack frame er en implementasjon som motsatt bruker andre "variabler" i tillegg til registeret esp (og disse andre "variablene" er praktisk talt alltid ebp (eller rbp i 64-bit)).

Fortsatt uproblematisk. Det er snakk om to mov. (push og pop av record)

 

Ved første øyekast vil allokering av dynamiske arrays på stacken kreve enten bruk av eksplisitt stack frame eller run-time patching av koden (hver gang man går inn i funksjonen) for aksessering av lokale variabler.

Det holder med én gang (mov esp ebp). Endret av Lycantrophe
Lenke til kommentar
Fortsatt uproblematisk. Det er snakk om to mov. (push og pop av record)

I tillegg til at man får ett mindre register å rutte med inne i funksjonen. På 32-bit x86 er det allerede ganske få.

 

Dessuten er jeg ikke enig i at det er uproblematisk med to mov, av prinsipp.

 

Eksempel:

Tenk deg nå at bar er en knøttliten funksjon der dette er signifikant. Om foo ikke bruker ebp kan ebp pushes i foo, bar kan kalles ti millioner ganger og ebp poppes (forutsetter en optimalisering av kompilatoren). Men om foo bruker ebp må bar selv bevare ebp.

 

De "bare" to ekstra mov forhindrer potensielt en bortoptimalisering av tjue millioner mov. Det skal sikkert et patologisk tilfelle til for å trigge det. :p Men jeg er patologien inkarnert.

 

Det holder med én gang (mov esp ebp).

Ved bruk av ebp er det ikke noe problem - annet enn at man bruker opp ebp! Om du overhodet ikke bruker ebp (kun esp) må koden patches på nytt hver gang størrelsen på arrayen endrer seg.

Endret av Emancipate
Lenke til kommentar

 

Fortsatt uproblematisk. Det er snakk om to mov. (push og pop av record)

I tillegg til at man får ett mindre register å rutte med inne i funksjonen. På 32-bit x86 er det allerede ganske få.

 

Få virtuelle. Det er langt flere fysiske.

 

Dessuten er jeg ikke enig i at det er uproblematisk med to mov, av prinsipp.

Det er dumt.

 

Eksempel:

Tenk deg nå at bar er en knøttliten funksjon der dette er signifikant. Om foo ikke bruker ebp kan ebp pushes i foo, bar kan kalles ti millioner ganger og ebp poppes (forutsetter en optimalisering av kompilatoren). Men om foo bruker ebp må bar selv bevare ebp.

Om funksjonen er triviell spiller det i praksis ingen rolle. Jeg tror du overestimerer hvor mange push/pop av ebp som skjer i optimaliserende kompilatorer.

 

De "bare" to ekstra mov forhindrer potensielt en bortoptimalisering av tjue millioner mov. Det skal sikkert et patologisk tilfelle til for å trigge det. :p Men jeg er patologien inkarnert.

Det er uansett bare noe som vil skje "ekstra" i tilfelle ene bruker scope-dynamiske arrays. Som uansett ikke bør gjøres i loops, og alternativet er uansett heap alloc som er -enda- dyrere.

 

Ved bruk av ebp er det ikke noe problem - annet enn at man bruker opp ebp! Om du overhodet ikke bruker ebp (kun esp) må koden patches på nytt hver gang størrelsen på arrayen endrer seg.

Neste gang du pusher ebp er ebp ledig igjen. Hele antagelsen din her er feil.
  • Liker 1
Lenke til kommentar

Som uansett ikke bør gjøres i loops, og alternativet er uansett heap alloc som er -enda- dyrere.

Utenfor eller innenfor loop spiller ingen rolle Det skjer selv om den dynamiske arrayen er deklarert utenfor loopen. Sjekk eksempelkoden min ovenfor. rbp er opptatt i hele foo, dersom man bruker dynamisk stakk-allokering.

 

Man kan ikke vite på generell basis om en enkelt heap alloc koster mer enn å "miste" ett register i hele funksjonen (selv om det normalt sett ikke gjør det).

 

Neste gang du pusher ebp er ebp ledig igjen.

Ja, men ebp må være tilgjengelig i hele foo. Altså har man "mistet" et register der, og mistet en mulighet for optimalisering.

 

 

 

Hele antagelsen din her er feil.

Egentlig ikke.

 

Antagelse: Tvinger dynamisk stack-array kompilatoren til å lage en eksplisitt stack frame?

Svar: Testing viser ja.

 

Edit: Det var mest en prinsippsak at artikkelen sa man skulle gjøre det sånn og sånn, uten å ta med konsekvensene av det.

Endret av Emancipate
Lenke til kommentar

Utenfor eller innenfor loop spiller ingen rolle Det skjer selv om den dynamiske arrayen er deklarert utenfor loopen. Sjekk eksempelkoden min ovenfor. rbp er opptatt i hele foo, dersom man bruker dynamisk stakk-allokering.

Som gjør det til en implisitt heap alloc, hvis forskjell er at selve mem-requesten allerede er gjort. Dette er høyt dynamisk kode og et ekstremt spesialtilfelle uansett, og alternativet er å manuelt malloce per iterasjon. Om du må manuelt alloc'e per iterasjon i en hot loop gjør du noe galt, men dette er generell (pseudo)mikrooptimalisering uansett, og et for svakt definert problem.

Man kan ikke vite på generell basis om en enkelt heap alloc koster mer enn å "miste" ett register i hele funksjonen (selv om det normalt sett ikke gjør det).

 

Ja, men ebp må være tilgjengelig i hele foo. Altså har man "mistet" et register der, og mistet en mulighet for optimalisering.

ebp er logisk, ikke fysisk. Det betyr ikke så mye i praksis.

 

 

Egentlig ikke.

Jo.

 

Antagelse: Tvinger dynamisk stack-array kompilatoren til å lage en eksplisitt stack frame?

Svar: Testing viser ja.

Ja, men den større implikasjonen er at du går tom for registre. Det er feil, det holder med -ett- fordi iterasjonene er sekvensielle.

 

Edit: Det var mest en prinsippsak at artikkelen sa man skulle gjøre det sånn og sånn, uten å ta med konsekvensene av det.

Artikkelen og reglene er rimelig skrøpelige.
  • Liker 1
Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...