Gå til innhold

David Brown

Medlemmer
  • Innlegg

    213
  • Ble med

  • Besøkte siden sist

Innlegg skrevet av David Brown

  1. Og til LonelyMan vil jeg nå si det er juks å implementere en ny algoritme, når målet er å sammenlikne C og assembly kode.

    Det blir som om man skulle teste 2 fly-motorer på 2 identiske fly, og en av partene plutselig satte motoren i et fly med bedre aerodynamisk design.

     

    Det blir en vurderingssak. Da jeg gav LonelyMan oppgaven sa jeg ikke noe om hvordan den måtte løses - jeg var mer interessert i å se beste løsning i assembler. Dvs. beste O(n)-løsning, å bruke fremgangsmåten beskrevt i denne kommentaren, eller bare skrive ut svaret direkte, ville selvsagt vært juls :)

     

    Jeg er interessert i å se hvordan man raskest og best kommer seg fra A til B, for å si det sånn, uavhengig av hvilke type fly eller motor vi snakker om. Så begge måter å sammenligne på gir interessant informasjon.

     

    Det var din oppgave, men likevel syns jeg du tar feil her (som du sier, det er en vurderingssak). Oppgaven - som jeg tolker det - var ikke å kalkulere summen. Som beskrevet i bl.a. Post #140, er den raskeste måten å gjøre kalkulasjonen å bruke litt matematikk og O(1) tid. Så enten er oppgaven "implementere denne algoritme i forskjellige språk", eller "kalkulere denne summen i forskjellige språk" - det er ikke helt meningsfullt å si "kalkulere denne summen i forskjellige språk, med hvilken som helst algoritme bare det er i O(n) tid". Etter min mening, dersom LonelyMan sin endringer i algoritme er tillatt, er også den O(1) løsningen tillatt. Kanskje t.o.m. C++ template løsningen min skulle være tillatt. (Og jeg sier dette til tross for at kompilatoren min implementerte LonelyMan sin algoritme raskere enn han gjorde i assembly.)

     

    Jeg tolker oppgaven som en implementeringsoppgave, ikke en algoritme oppgave. Skal vi jobbe med algoritmer, må du finne på en oppgave som ikke har en så pass enkel løsning!

  2. Hehe, artig lesing i denne tråden.

    Men jeg må si meg enig med David Brown - en kompilator sitter på sabla mange gode triks, og skal man klare å skrive raskere kode i assembly, må man ha en veldig smart løsning til et spesialisert problem, slik at man kan gjøre noe spesielt kompilatoren ikke tenkte på.

     

    Og til LonelyMan vil jeg nå si det er juks å implementere en ny algoritme, når målet er å sammenlikne C og assembly kode.

    Det blir som om man skulle teste 2 fly-motorer på 2 identiske fly, og en av partene plutselig satte motoren i et fly med bedre aerodynamisk design.

     

    Korrekt.

     

    Det fins en tid og plass til assembly - jeg har selv skrevet i assembly i nærmere 30 år på forskjellige prosessorer. Poenget mitt er ikke å bevise at C er alltid raskere enn assembly, fordi det ikke er sant. Poenget mitt er at i de fleste tilfelle, er C et bedre valg enn assembly av veldig mange grunn - og at det faktisk ofte er omtrent like raskt, eller ennå raskere, enn programmene man skriver på assembly. Hvis man lege mye arbeid i tenking, måling, prøving og feiling, kan man skrive assembly kode som er raskere enn det kompilatoren klarer. Men man gjør ikke det i praksis, uten at man har veldig gode grunn. Det er bare så mye mer arbeid dersom man skal lage noe i assembly som er klar, vedlikeholdsvennlig, og bevislig korrekt.

     

    For å gjøre C koden komplett til en profesjonelt standard ville C koden i denne tråden bare trenger et par kommentarlinje som sier hva funksjonen skal gjøre - virkemoten er selvforklarende. Men assembly koden hadde trengt minst noen sider med dokumentasjon. Og alt sammen måte gjøres på nytt for å kjøre koden på en ny prosessor - det trengs endringer bare for å kjøre på 64-bit modus på samme prosessor.

     

    En del programmer drar nytte av assembly. Hvis du ser inn i koden til VLC eller andre media spillerer, de innerste loopene i codec'ene er ofte i assembly (eller rettere sagt har de assembly implementasjonene i tillegg til C kode, for å dekke flest mulig cpu'er). Skal man få det meste ut av SIMD, må man som regel ti til assembly. Ser du inn i koden til en operativsystem, ser man også en del assembly her og der. Men disse er små bitter av koden, der man har store gevinst av tunge optimisering av bestemte oppgaver.

     

    Tiden da det var fornuftig å bruke assembly som hovedspråk i generelle programmering er for lengst forbi i PC verden. Det samme gjelder til et mindre grad også C og også C++ - kan man gjøre samme oppgaven med raskere utvikling med et høyre nivå språk med bedre sikkerhet mot programmeringsfeil, burde man som regel velge slike språk. Forbedret JIT kompilatorer og andre tekniske forbedringer redusere kostnadene for mer avanserte språk. (Men det er viktig at mange av bibliotekene til slike språk skrives i C/C++, for å holde opp gjennomsnitt hastighet.) Det meste av min PC programmering gjør jeg på Python.

     

    Balansene er annerledes med andre prosessorer. Noen av de jeg bruker eller har brukt er dårlig tilpasset til C - hvis jeg brukte C istedenfor assembly, måte jeg velge en større, raskere og dyrere brikke. Da er assembly et solid valg.

     

    Et annet punkt i denne tråden er hvorfor man skulle lære assembly. Jeg liker veldig godt at programmerere lærer assembly på de prosessorene de skal bruke - det gir en mye bedre forståelse for brikken, og man skriver langt bedre og raskere kode i C som resultat. Man blir en bedre sjåfør av å ha litt forståelse av hvordan bilen og motoren fungere, selv om man ikke blir bilmekaniker.

    • Liker 2
  3. David Brown, ny test: (Fjerde test)

     

    243 ms på en 2600K overklokket prosessor på 4 GHz med den nøyaktig samme koden du la ut, full optimalisering spesifisert i kompilatoren.

     

    he-he (den prosessoren er ikke noe småtull heller)

     

    Når du påstår at du:

     

    1: Fikk 24 ms på en 2.5 GHz prosessor med den gitte koden, og jeg får

    2: 243 ms på en 4 GHz prosessor, så må du skjønne at jeg ikke kan ta deg seriøst

     

    Jeg regner med det er post nummer #127 av meg som du ikke forstår. La meg prøve å forklare igjen, og poster C koden for å gjøre det klinkende klart.

     

    Vi ble enige om å implementer en funksjon med "if (i % 3 == 0 || i % 5 == 0)" testen. Du implementerte den i assembly, jeg implementerte den i C (d.v.s., kopi-og-lim fra bloggen). Din forsøk på dette med en nokså klar assembly kode tok 0.858 s hos deg, og 0.978 s på min i7-920 maskin. Min forsøk med C tok 0.254 s (med 64-bit kompilator - selv om det var ubetydelig). Ref post #101.

     

    Deretter endret du algoritmen, og fikk 31 ms (ref post #108). Jeg sa i post #127 at dette var meningsløs til sammenligning fordi det er ikke samme algoritme. Og for å forklare videre sa jeg at da jeg implementere samme algoritme som deg, tok det 34 ms på min PC. Kompilatoren min lagte nesten identisk kode til den du hadde skrevet, og kjøringstiden var omtrent identisk (etter skalering fordi din PC er litt raskere enn den i7-920 jeg testet på). Jeg noterte også at dersom jeg presset kompilatoren litt mer med "-funroll-all-loops", kjørte den i 26 ms host meg. Jeg postet ikke C koden min - jeg så ingen vits i å fylle plassen med koden, ettersom det er temmelig klar når man først har forstått algoritmen i din assembly kode. Men her er den:

     

    static int sum3or5(int m) {

    int sum = 0;

    int i = 3;

    while (i < m) {

    sum += i;

    i += 3;

    }

    i = 5;

    while (1) {

    sum += i;

    i += 5;

    if (i >= m) return sum;

    sum += i;

    i += 10;

    if (i >= m) return sum;

    }

    }

     

     

    Jeg er ikke vant til at folk ikke tror på meg. Alle kan ta feil inn i blant, og man skal være skeptisk til veldig overraskende resultater. Men utgangspunkt skal vanligvis være at dersom "motstanderen" sier noe man kan ikke helt tro er mulig, er det fordi noen har misforstått noe. Tror du virkelig at jeg har skrevet alle disse poster med løgn, bare for å plage deg? Jeg syns faktisk det er interessant å sammenligne assembly og C som språk, og jeg bruke begge i arbeid på mange forskjellige prosessorer. Jeg var nysgjerrig om påstandene dine om hastigheter til assembly programmering på x86 faktisk holdt mål - x86 prosessor er ganske komplisert, så det er mulig at i assembly programmering du kan bruke triks som kompilatoren ikke kan. Men det virker temmelig klar at dette ikke er tilfelle med denne oppgaven - kompilatoren lager raskere koden enn deg til samme algoritme.

    • Liker 1
  4. Nå har jeg kompilert koden din på en ren x86 plattform og den kjørte på 1 sekund blank med full optimalisering. Commandline options: -Tx86-coff -Ot -Ob1 -fp:precise -W1 -Gd

     

    Bare sånn at du ser forskjellen på ren x86 kode med 8 registre. :thumbup:

    Så du har ikke mye troverdighet igjen nå, og dermed tar jeg kvelden (eller formiddagen av forumet) Ha en god dag. :tease:

     

    static int sum3or5(int m) {
    int sum = 0;
    for (int i = 0; i < m; i++) {
    if (i % 3 == 0 || i % 5 == 0) {
    sum += i; }
    }
    return sum;
    }
    
    int main(void) {
    printf("%i\n", sum3or5(100000000));
    return 0;
    }
    

     

    Hvilken kompilatoren brukte du her? Jeg har bare brukt gcc på x86 platform, men en forsøk på google på kommandolinje switches viser at det kanskje er noe som heter "Pelles C compiler" du har brukt, som er basert på LCC. LCC regnes som en liten og enkel kompilator, som er godt skrevet og lett å forstå, men ingen tungvekt når det gjelder å lage effektivt kode. Skal du lage raskt kode fra C, trenger du en voksen kompilator - gcc, llvm, Intel sin kompilator, MS VC++, etc.

  5. Det kalles for loop unrolling, og det er noe som er tungvindt å gjøre manuelt i assembly, men enkelt å gjøre i en kompilator. Det er ikke alltid at det øker hastighet - det er en balanse mellom pipelining, branch kostnader,

     

    Den koden du nettopp la frem kjører på 32 ms, min kjørte på 31 så her har du bomma kraftig, optimalisering er helt klart ikke din ting, det skal du overlate til kompilatoren din. Ja jeg er fullt klar over hva loop unrolling er, om det er tungvint eller ikke har ingenting med testen å gjøre. Poenget er når jeg sier "det er ikke mer å unrolle" så betyr det at det ikke er relevant eller at algoritmen er laget slik at det ikke er mulig å gjøre det bedre. Og det har jeg nettopp vist det, din kode kjørte 1 ms tregere, mens det du påstår skulle gjort at den kjørte noen millisekunder raskere, hvilket det ikke gjorde.

     

    Jeg har ikke misforstått her, men det har du. Kanskje jeg ikke var særlig klar i mine forklaringer. Jeg skulle vise deg hva "loop unrolling" betyr, og en eksempel over hvordan denne koden kunne "unrolles" - jeg prøvde ikke å gi ferdig kode, og testet ikke koden. Når jeg ba kompilatoren til å kjøre ekstra loop unrolling, gjorde den noe omtrent sånt - men ikke akkurat som jeg skrev. Kompilatoren kjenner de detaljene som trengs for å få lagt raskere kode, men jeg brukte en eksempel som var lettere å forstå for å illustrere poenget.

     

    Og ja, det <i>er</i> mulig å unrolle koden mer og få raskere kode. Ihvertfall klarte kompilatoren min det uten problem. Som sagt, er kompilatoren sin kode litt mer komplisert enn den enkel eksempel jeg ga deg.

     

    Og dersom du noen gang prøve å jobbe med en annen cpu som ikke er like register-fattig som x86

     

    Vel, om vi skal måle c++ opp mot asm, så må vel begge bruke x86, jeg blir så frustrert av å lytte til dette. Det var c++ mot asm, ikke AT&T reversert notasjon med doble registre. Det du sier om register-fattig, det er ikke en negativ ting for meg, det er en positiv i mitt favør, da vi skulle måle c++ mot asm, og du behøver doble registre.

     

    Problemet ditt med forståelsen av ekstra registre er at du ikke skjønner "impacten" av å ha ekstra registre.

     

    Om det plager deg at jeg brukte 64-bit kompilator, kan jeg begrense meg til en 32-bit kompilator i framtiden i tråden. Jeg er enige at den mest korrekte sammenligningen får vi ved å holde oss til samme modellen - selv om jeg vet (og har bevist gjennom testing) at det er nokså lite forskjell i dette tilfelle, der man kan ikke dra særlig fordel av ekstra registerene.

  6. Og det jeg sa om at programstrukturen er likt på begge arkitekturene er selvfølgelig sant.

     

    Nei det er ikke det. Du har brukt øyet ditt til å analysere om det er likt, og øyet ditt søker etter om "bildet" av de to sammenligningene ser likt ut. Du bruker øyet til å kjapt skanne over instruksjonene for å se om bildet er likt, men dette har ingen verdens ting å gjøre med hva som skjer i maskinen. :cool:

     

    Har du virkelig analysert koden til punkt og prikke? Hvordan likestiller du de to, hvilke kriterier setter du for å måle og sammenligne en elefant og en flodhest?

     

    Det å "Se" med øyet på koden om den er "mer eller mindre lik" er jo katastrofalt, det er årsaker som dette at man burde laget offisielle regler for dette.

     

    Du kan virkelig ikke sammenligne eller forsøke å erstatte registre (mentalt) og så tenke (aha, dette er jo en liknende sak, så den er jo lik med den andre). Det er katastrofalt :tease:

     

    Men jeg føler at jeg har tingene på min side så jeg sier takk for diskusjonen, nå må jeg kode :tease:

     

    Jeg er enige at jeg bare har brukt øyene for å sammenligne koden. Men jeg ber deg se på de to kodelistingene i post #141, og sammenligne dem selv. Det fins et par forskjeller - den som er på 64-bit tar fordelen av ekstra registerer for å lagre en konstant i register istedenfor å bruke en immediate konstant senere. Dette fører også til litt forskjeller i register bruk. Men ellers er koden identisk.

     

    Jeg er fullstendig klar over at kode som virker overfladisk likt kan ha betydelige forskjeller i kjøringshastighet. Alt som trengs er en uventet dependency i registers, eller forskjeller i hvordan prosessoren kan benytte seg av de forskjellige instruction units. Jeg ser ingen slike problemer her. Jeg er ingen ekspert på x86 assembly - og selv de alle mest erfaren x86 assembly programmerere har ikke alle detaljer til alle x86 cpu'er i hodet. Men ved å teste koden bekreftet jeg at det er ihvertfall ikke store forskjeller.

  7. Overflow er ikke ett problem for å måle hastigheten. Men om noe annet så ser jeg en veldig interessant sak i koden din, det gjør ting litt suspekte, men jeg skal ikke si noe sikkert, det er uansett irrelevant da vi snakker to forskjellige ting her uansett så det blir bare bagateller i en sak som allerede er så til de grader satt ut i proporsjoner.

     

    Er det kult at du ikke klare å ta i bruk mer enn halvparten av prosessoren din?

     

    Punkt 1: Jeg KAN ta i bruk mer enn halvparten av prosessoren. Men jeg gjør det ikke under omstendigheter hvor det ikke er mulig.

     

    Punkt 2: Du kjører i bakoverkompatibel modus, det er ikke REN x86, forstår du hva det betyr? Det betyr at du har dobbelt opp med registre å bruke i en modus som er "fiktiv" i x86 verdenen.

     

    Punkt 3: Når du fikk til 0.2 sekunder som du sa i tidligere innleg (altså over 200 millisekunder) og jeg klarte på 31 millisekunder, når du da kompilerer samme koden på ett annen maskinvaresett og får 34 ms (hvis jeg husker rett) så er det jo noe riv ruskende galt.

     

    Men hovedproblemet her er at du kjører en kode på en annen arkitektur enn min kode så det blir ikke sammenlignbart, i tillegg så fikk du mer eller mindre samme resultat så konklusjonen er den samme.

     

    DavidBrown, jeg vil at du skal poste kildekoden i c++ da du fikk

    256 millisekunder og koden da du fikk 34 millisekunder. Ikke debugger kode, men kildekoden.

     

    Jeg er enige at overflow er ikke et stort problem her - vi kan late som om problemet er definert modulo 2^32 og konsentrere oss om hastighet. Men det er likevel viktig å poengtere det - det er ikke vits i et program som kjører fort men gir feil svar.

     

    Jeg kjører ikke i "bakoverkompatibel" modus. Jeg har gjort de fleste av forsøkende mine i renn amd64 modus (eller "x86-64" modus - men jeg foretrekker å gi AMD kreditt for å ha dratt pc verden motvillig framover). Hvis du ser på koden - som jeg postet i #141 - ser du at kompilatoren bruker både ekstra registers (r8d og r9d) og 64-bit register (rdx). Det vil si, den bruker "64-bit mode". Når jeg testet med 32-bit, kjørte jeg på en 32-bit virtuell maskin på samme PC. Da er det gode gammeldags "protected mode", noe som er klar fra koden jeg postet.

     

    <http://en.wikipedia.org/wiki/X86-64#Operating_modes>

  8. David Brown, som sagt du kjører et 64 bits der med 16 registre. Mitt program er ett 32 bits program med 8 registre, altså halvparten av hva du bruker der. Du kan jo på ingen måte sammenligne det der, men det mest tragiske var at jeg likevel hadde samme hastighet enn dog du har dobbelt så mange registre tilgjengelig, det er helt latterlig. Hva jeg kunne gjort med disse ekstra registrene :cool:

     

    Er det kult at du ikke klare å ta i bruk mer enn halvparten av prosessoren din?

     

    Men bare for å glede deg, har jeg gjort kompilering med både 64-bit og 32-bit. Dette er på en annen maskin, så timingene er litt raskere enn før - 64-bit tok 0.186 s, mens 32-bit var faktisk litt raskere på 0.179 s. Og som jeg sa før, er assembly koden nesten identisk. (Dette var med original programmet fra bloggsiden.)

     

    Vi kan se noe annet interessant dersom vi bruker 64-bit integer til summen i programmet - noe vi burde egentlig gjøre når vi finner sum3or5(100000000) for å få det rette svaret uten overflow. Med 64-bit kompilator tar det nå 0.169 sekund - raskere enn for å finne feil svar. Men med 32-bit kompilator tar det 0.237 sekunder.

     

     

    Min konklusjon er:

     

    1: Din forrige kode sa du var 32 bits kode (men i det nye eksemplet ditt her så er det 64 bits "kode") jeg føler at du ikke snakker sant selv om du kaller det bakoverkompatibelt så er det dobbelt opp med registre og det kan nesten kvalifiseres som 64 bits, derfor sier jeg det for å unngå forvirring.

     

     

    Jeg tar som gitt at du tror jeg snakker sant, med forbehold at jeg kan ha tatt feil - jeg rettet feilen da jeg så den. Husk at det er svært sjelden jeg har sett på x86 assembly - jeg la ikke merke til bruk av registerene. Jeg liker ikke implikasjonen din at jeg bevist ikke snakket sant.

     

    Og det jeg sa om at programstrukturen er likt på begge arkitekturene er selvfølgelig sant.

     

     

    2: Du tar min algoritme og implementerer den.

     

    3: Du implementerer den på ett hardware sett som har dobbelt så mange registre og er ikke engang et 32 bits program.

     

    Det er helt klart og utvilsomt ett slag som er tapt fra din side, utvilsomt.

     

     

    Jeg regner med etter de postene jeg har skrevet i det siste, at du er ikke lengre like bevist.

     

    Men får å gjøre det enkelt for deg, her er C programmet med den algoritmen vi skulle implementere ifølge utfordringen og bloggen. Jeg har gjort alle kompilering med gcc 4.5, som er lett tilgjengelig på alle systemer dersom du vil prøve det selv. Kommandolinjen er:

     

    gcc euler.c -o euler -std=gnu99 -O2 -Wa,-ahdls=euler.c.lst

     

    Koden (den som gir feil verdi p.g.a. overflow):

     

    #include <stdint.h>

    #include <stdbool.h>

    #include <stdio.h>

     

     

    static int sum3or5(int m) {

    int sum = 0;

    for (int i = 0; i < m; i++) {

    if (i % 3 == 0 || i % 5 == 0) {

    sum += i; }

    }

    return sum;

    }

     

    int main(void) {

    printf("%i\n", sum3or5(100000000));

    return 0;

    }

     

     

    Genererte assembly på 32-bit:

     

    9 main:

    10 0000 55 pushl %ebp

    11 0001 31C9 xorl %ecx, %ecx

    12 0003 89E5 movl %esp, %ebp

    13 0005 83E4F0 andl $-16, %esp

    14 0008 57 pushl %edi

    15 0009 BF565555 movl $1431655766, %edi

    15 55

    16 000e 56 pushl %esi

    17 000f 31F6 xorl %esi, %esi

    18 0011 53 pushl %ebx

    19 0012 83EC14 subl $20, %esp

    20 .p2align 4,,7

    21 0015 8D7600 .p2align 3

    22 .L4:

    23 0018 89C8 movl %ecx, %eax

    24 001a 89CB movl %ecx, %ebx

    25 001c F7EF imull %edi

    26 001e C1FB1F sarl $31, %ebx

    27 0021 29DA subl %ebx, %edx

    28 0023 8D0452 leal (%edx,%edx,2), %eax

    29 0026 39C1 cmpl %eax, %ecx

    30 0028 7412 je .L2

    31 002a B8676666 movl $1717986919, %eax

    31 66

    32 002f F7E9 imull %ecx

    33 0031 D1FA sarl %edx

    34 0033 29DA subl %ebx, %edx

    35 0035 8D0492 leal (%edx,%edx,4), %eax

    36 0038 39C1 cmpl %eax, %ecx

    37 003a 7502 jne .L3

    38 .L2:

    39 003c 01CE addl %ecx, %esi

    40 .L3:

    41 003e 83C101 addl $1, %ecx

    42 0041 81F900E1 cmpl $100000000, %ecx

    42 F505

    43 0047 75CF jne .L4

    44 0049 89742404 movl %esi, 4(%esp)

    45 004d C7042400 movl $.LC0, (%esp)

    45 000000

    46 0054 E8FCFFFF call printf

    46 FF

    47 0059 83C414 addl $20, %esp

    48 005c 31C0 xorl %eax, %eax

    49 005e 5B popl %ebx

    50 005f 5E popl %esi

    51 0060 5F popl %edi

    52 0061 89EC movl %ebp, %esp

    53 0063 5D popl %ebp

    54 0064 C3 ret

     

    Genererte assembly på 64-bit:

     

    9 main:

    10 .LFB12:

    11 .cfi_startproc

    12 0000 4883EC08 subq $8, %rsp

    13 .cfi_def_cfa_offset 16

    14 0004 31F6 xorl %esi, %esi

    15 0006 31C9 xorl %ecx, %ecx

    16 0008 41B85655 movl $1431655766, %r8d

    16 5555

    17 000e 41B96766 movl $1717986919, %r9d

    17 6666

    18 .p2align 4,,10

    19 0014 0F1F4000 .p2align 3

    20 .L4:

    21 0018 89C8 movl %ecx, %eax

    22 001a 89CF movl %ecx, %edi

    23 001c 41F7E8 imull %r8d

    24 001f C1FF1F sarl $31, %edi

    25 0022 29FA subl %edi, %edx

    26 0024 8D1452 leal (%rdx,%rdx,2), %edx

    27 0027 39D1 cmpl %edx, %ecx

    28 0029 7410 je .L2

    29 002b 89C8 movl %ecx, %eax

    30 002d 41F7E9 imull %r9d

    31 0030 D1FA sarl %edx

    32 0032 29FA subl %edi, %edx

    33 0034 8D1492 leal (%rdx,%rdx,4), %edx

    34 0037 39D1 cmpl %edx, %ecx

    35 0039 7502 jne .L3

    36 .L2:

    37 003b 01CE addl %ecx, %esi

    38 .L3:

    39 003d FFC1 incl %ecx

    40 003f 81F900E1 cmpl $100000000, %ecx

    40 F505

    41 0045 75D1 jne .L4

    42 0047 BF000000 movl $.LC0, %edi

    42 00

    43 004c 31C0 xorl %eax, %eax

    44 004e E8000000 call printf

    44 00

    45 0053 31C0 xorl %eax, %eax

    46 0055 4883C408 addq $8, %rsp

    47 .cfi_def_cfa_offset 8

    48 0059 C3 ret

     

     

    Bortsett fra prologue og epilogue, er det eneste fordelen med 64-bit versjonen at begge de to "imull" konstantene er lagret i register.

  9. Første poeng er at det er viktig at du poster kode når du legger ut tall når du sammenligner.

     

    Jeg trodde jeg hadde postet en god del av koden. Jeg har slurvet litt med å poste C koden - men det er bare når det er soleklart hvordan C koden skal være, eller andre har også postet koden. Men mangler du noe bestemt, så kan jeg godt poste den. Jeg er selvfølgelig enige med det at man må ha koden for å kunne kjøre det på samme maskin, for å kunne sammenligne tider.

     

    Neste poeng er at fordi jeg tenker ut bedre algoritmer enn deg, så blir ikke det automatisk juks, din algoritme var elendig og nå har du kopiert min, legger ut tall som ikke samsvarer med noen kode.

     

     

    Joda, det er "juks" i denne sammenhengen. Jeg har en betydelige bedre algoritme enn deg - og jeg så den med en gang jeg leste utfordringen på bloggen. Men som andre har sagt, var ikke det poenget her, fordi en raskere algoritme viser ingenting i sammenligning mellom C og assembly. Utfordringen var for å gi deg sjanse til å overbevise andre at du kan skrive koden raskere i assembly enn andre kan i C - ikke for å se hvem som kan finne en bedre algoritme.

     

    Koden i C er:

     

    static int sumn(unsigned int n) {

    // Return sum of 1 + 2 + .. + (n-1)

    return (n * (n - 1)) / 2;

    }

     

    static unsigned int sum3or5(unsigned int n) {

    unsigned int n3 = (n + 2) / 3;

    unsigned int n5 = (n + 4) / 5;

    unsigned int n15 = (n + 14) / 15;

    return 3 * sumn(n3) + 5 * sumn(n5) - 15 * sumn(n15);

    }

     

    int main(void) {

    printf("%i\n", sum3or5(1000000000));

    return 0;

    }

     

    Men optimisering på, er den genererte assembly koden en instruksjon "movl $2138801452, %esi". Det vil si, kompilatoren er smart nok til å gjøre alle kalkulasjonene på forhånd siden talene er konstant. Det går ikke an å lage noe bedre koden enn det.

     

    Dersom jeg bruker ekstra "volatile" variabler og begrenset optimisering for å tvinge kompilatoren å generere koden til kalkulasjon, tok den hele 8 nanosekunder å kjøre "sum3or5" funksjonen.

     

    Er det lov for deg å endre algoritme, så er det lov for meg å gjøre det. Og jeg gjør det på en måte som er lite avhengig av de faktiske tall som vi bruker her - det er kompilatoren som gjør grovarbeidet.

     

     

    Det tredje poenget er at du ikke kan sammenligne 64 bits kode med 32 bits kode på noen måter.

     

    Joda, vi kan sammenligne dem. For det første er de fleste instruksjonene nokså likt. Det ville ha vært noen forskjell dersom jeg hadde brukt en 32-bit kompilatoren, men strukturen i genererte koden hadde vært likt. Statistisk sett er det få programmer som kjører mer enn 10-20% fortere etter omkompilering til 64-bit sammenlignet på 32-bit på samme systemet.

     

    For det andre har 64-bit vært den kurante arkitekturen i PC verden i mange år - jeg kan ikke gjøre noe for at du er begrenset til gammeldags teknologi.

     

    For det tredje, handler dette tråden mye om hvor egnet eller uegnet assembly er til generelle programmering, og faktum at du er ikke i stand til å benytte kraften i prosessoren din p.g.a. begrensninger til assembly er et veldig viktig poeng. Du kan ikke påstå at du kan skrive det raskeste mulig programmer til PC'en din, når du faktisk ikke bruke prosessoren.

     

     

    Det fjerde poenget er at koden allerede ER rollet ut, det du sier høres veldig merkelig ut, det fins ikke flere registre å rolle ut i dette tilfellet. Så jeg er svært spent på å se koden. :thumbup:

     

    De konstantene du snakker om kalles magic numbers. Jeg er godt kjent med de, men har ikke brukt de.

     

    Nei, koden er ikke rollet ut. Du har rullet ut litt i koden din, men man kan gjøre det mye mer. For å ta den første loopen din:

     

    n1: add eax, ebx

    add ebx, edx

    cmp ebx, ecx

    jbe n1

     

    Da har du en backover hopp for hver rundt, noe som koster i pipelining ytelse. En mer effektivt koding hadde vært:

     

    n1: add eax, ebx

    add ebx, edx

    cmp ebx, ecx

    ja n2

    add eax, ebx

    add ebx, edx

    cmp ebx, ecx

    ja n2

    add eax, ebx

    add ebx, edx

    cmp ebx, ecx

    ja n2

    add eax, ebx

    add ebx, edx

    cmp ebx, ecx

    jbe n1

    n2:

     

    Det kalles for loop unrolling, og det er noe som er tungvindt å gjøre manuelt i assembly, men enkelt å gjøre i en kompilator. Det er ikke alltid at det øker hastighet - det er en balanse mellom pipelining, branch kostnader, branch prediction buffers, cache linjer, prefetch buffers, osv. Kompilatoren har en nokså god ide om dette for alle de forskjellige prosessorene, men det blir litt "trial and error". Fort gjort med en kompilator der man bare endre noen command line switcher, men ikke så morsomt i assembly verden.

     

    Og dersom du noen gang prøve å jobbe med en annen cpu som ikke er like register-fattig som x86, finner du fort ut hvor flott det er å ha en kompilator som holde redde på alle register - dette type unrolling kan nemlig la kompilatoren kjøre interleaving på itterasjonene ved å bruke forskjellige registerer og dermed unngå stalls p.g.a. dependencies.

     

    (Jeg beklager at det blir en del engelske uttrykk her - dette er ikke noe jeg pleier å snakke om på norsk.)

    • Liker 1
  10. En algoritme som bruker konstant tid på å gjøre noe.

    Slik som å utføre noen beregninger som tar like lang tid uansett input.

     

    Er noe O (n) sier man det tar lineær tid. Da er antall operasjoner som må gjøres proporsjonal med input.

    F. eks det å finne minste tall i en liste. Da må du gå igjennom hele listen, og blir listen større tar det tilsvarende lang tid. Altså O (n)

    Skal du finne første element i en liste spiller det jo ingen rolle hvor mange elementer som kommer etterpå, så det er O (1)

    Sorteringsproblemer er typisk O (n*n)

     

    Sortering er vanligvis O(n.log(n)) tid, ihvertfall med vanlige effektive algoritmer (heap sort, quick sort, insertion sort). Det fins O(n^2) sorting algoritmer, som bubblesort, som er greit når du trenger noe liten og enkelt, og ikke skal sortere store lister. Det fins også algoritmer som nærmer seg O(n), som bucket sort, men de er kun bedre enn O(n.log(n)) i bestemte omstendigheter.

  11. Det hadde også godt an å bruke en bedre generelle algoritme som fungere i O(0) tid istedenfor O(1) tid - men igjen så ville det ikke være sammenlignbar.

    Jeg kan big-O notasjon, men aldri vært borti noe annet enn konstant tid O(1), hva skal O(0) bety?

    jeg antar han mente O(1) og O(n), ikke O(0) og O(1)... :|

     

    O(0) ville i såfall bety en algoritme uten instruksjoner i det hele tatt :)

     

    Korrekt - beklager feilskrivingen min. Du kan tenke at jeg mente å skrive O(n^1) og O(n^0) - det var ihvertfall det som var tanken.

  12. Jeg lar deg lekke litt mer med assembly'en før jeg fortelle om trikset kompilatoren brukte for å lage raskere kode.

     

    Nå kan du fortelle om trikset som gjorde koden din raskere enn min. :thumbup:

     

    Deling er en ganske treg operasjon på x86 cpu'er (og de alle fleste andre cpu'er), og passer veldig dårlig inn i pipelines. Dermed bruker gode C kompilatorer (som gcc) ganging istedenfor deling ved en konstant. Det er ikke lett å finne ut de beste konstanter å bruke, og være sikkert på at det er riktig, spesielt dersom man bruker signed arithmetic - derfor er det best å la kompilatoren gjør jobben. Koden under er fra listing filen fra kompilatoren (jeg tok feil tidligere, da jeg sa at den ikke brukte ekstra amd64 register - men koden til 32-bit x86 hadde vært veldig likt):

     

    123 test1000:

    124 .LFB15:

    125 .cfi_startproc

    126 00c0 31FF xorl %edi, %edi

    127 00c2 31C9 xorl %ecx, %ecx

    128 00c4 41B85655 movl $1431655766, %r8d

    128 5555

    129 00ca 41B96766 movl $1717986919, %r9d

    129 6666

    130 .p2align 4,,10

    131 .p2align 3

    132 .L20:

    133 00d0 89C8 movl %ecx, %eax

    134 00d2 89CE movl %ecx, %esi

    135 00d4 41F7E8 imull %r8d

    136 00d7 C1FE1F sarl $31, %esi

    137 00da 29F2 subl %esi, %edx

    138 00dc 8D1452 leal (%rdx,%rdx,2), %edx

    139 00df 39D1 cmpl %edx, %ecx

    140 00e1 7410 je .L18

    141 00e3 89C8 movl %ecx, %eax

    142 00e5 41F7E9 imull %r9d

    143 00e8 D1FA sarl %edx

    144 00ea 29F2 subl %esi, %edx

    145 00ec 8D1492 leal (%rdx,%rdx,4), %edx

    146 00ef 39D1 cmpl %edx, %ecx

    147 00f1 7502 jne .L19

    148 .L18:

    149 00f3 01CF addl %ecx, %edi

    150 .L19:

    151 00f5 83C101 addl $1, %ecx

    152 00f8 81F9E803 cmpl $1000, %ecx

    152 0000

    153 00fe 75D0 jne .L20

    154 0100 89F8 movl %edi, %eax

    155 0102 C3 ret

  13. Seien Sie so gut... 31 ms, 100 mill. :!:

     

    OPTION PROLOGUE:NONE
    OPTION EPILOGUE:NONE
    ALIGN 4
    Multipler PROC
           push ebx
           mov ebx, 3
           xor eax, eax
           mov ecx, 100000000
           mov edx, ebx
    ALIGN 16
    n1:	add eax, ebx
           add ebx, edx
           cmp ebx, ecx
           jbe n1
           mov ebx, 5
           mov edx, ebx
    ALIGN 16
    n2:	add eax, ebx
           add ebx, edx
           cmp ebx, ecx
           ja n3
           add eax, ebx
           add ebx, edx
           add ebx, edx
           cmp ebx, ecx
           jbe n2
    n3:	pop ebx
           ret
    Multipler ENDP
    OPTION PROLOGUE:PROLOGUEDEF
    OPTION EPILOGUE:EPILOGUEDEF
    

     

     

    Et problem med den originale oppgaven er at det er åpent til bedre implementasjoner og algoritmer. For å være ihvertfall litt meningsfullt, er det derfor viktig å holde seg til samme struktur. Man skal kjøre gjennom alle tall opp til 1000 (eventuelt en annen tall), se om det kan deles ved 3 eller 5, og legge sammen tallene. Det gjør du ikke her. Det er klart at din algoritme her er raskere enn originalen, og gir samme resultantene, men det kan ikke sammenlignes med C koden. Dersom man får lov til å endre algoritmen, er min C++ template kode like gyldig - og der bruker man kompilatoren til å kalkulere resultantene på forhånd, slik at koden er bare en instruksjon. Det hadde også godt an å bruke en bedre generelle algoritme som fungere i O(0) tid istedenfor O(1) tid - men igjen så ville det ikke være sammenlignbar.

     

    Enten må vi holde oss til akkurat samme algoritme, med optimisering av implementasjon men ikke metoden, ellers må vi finne et annet problem som ikke har samme forbedringsmuligheter.

     

    Så for min del er ikke denne koden aksepterte som assembly implementasjon av oppgaven - du har optimisert algoritmen, ikke laget en assembly implementasjon som slå C implementasjon.

     

    Forresten da jeg skrevet samme implementasjon som deg i C, laget kompilatoren omtrent identiske kode som kjørte på omtrent samme tid (34 ms på min PC). Men jeg kunne også gi kompilatoren en ekstra "-funroll-all-loops" flag som reduserte tiden til 26 ms - en 20% økning i hastighet for 10 sekunds arbeid.

    • Liker 1
  14. Kjører du på 64 bits med mitt program så blir det ikke det samme, og kompilerer du ett 64 bits c++ program så vil den fungere på en annen måte. Årsaken til at det muligens kan være raskere er fordi kompilatoren din benytter SSE til å kalkulere modulus og muligens kjører over flere tråder. "Feil" resultat skyldes at resultatet tolkes som signed dword, du kan ikke kalkulere 100 millioner tall og forvente at det skal akkumuleres opp til ett reelt tall, det fungerer kun for å måle hastigheten, ikke for å få ett rett resultat. Jeg kommer tilbake senere med en tilsvarende SSE variant, men har dårlig tid nå.

     

    Koden laget av kompilatoren brukte ikke SSE, og selv om det var på 64-bit er det ingenting i den genererte assembly koden som bruker hverken 64-bit arithmetic eller de ekstra registerene som er tilgjengelige. Jeg regner med at koden fra 32-bit gcc ville være praktisk sett identiske.

  15. Hvilken assembler bruker du? Kunne vært interessant å prøve ut koden din...

     

    Her er C-kode for å gjøre det samme:

     

    #include <stdio.h>
    
    int main() {
     int sum = 0;
     int i;
     for(i=0; i<100000000; i++)
       if (i % 3 == 0 || i % 5 == 0) sum += i;
     printf("%d\n",sum);
    }
    

     

    Den bruker omtrent 1465 ms på maskinen min.

     

    Ada-kode:

     

    with Ada.Text_IO;
    
    procedure Euler1 is
      Sum : Integer := 0;
    begin
      for i in 0..99999999 loop
         if (i mod 3 = 0) or else (i mod 5 = 0) then
            Sum := Sum + i;
         end if;
      end loop;
      Ada.Text_IO.Put_Line(Integer'Image(Sum));
    end Euler1;
    

     

    Denne bruker 1281 ms. Prosessoren koden kjører på er en Intel Core 2 U7300 (1.3 GHz).

     

    Jeg tror han bruker MS sin assembler på 32-bit Windows, men det er bare antagelse ut fra tidligere poster. Da jeg prøvde med koden hans brukte jeg gcc (d.v.s., gas i praksis) på 64-bit Linux. Som sagt, fikk jeg koden til å kjøre men med feil svar.

     

    Det er interessant at Ada var raskere enn C. Er det gcc/gnat du bruker? Og er det store forskjeller i versjonene? Har du litt optimisering på (-Os eller -O2)? Det er mulig at default optimisering er forskjellige mellom C og Ada.

  16. Første steg i optimaliseringen, nå er koden 9% raskere og kjører på 858 ms. (100 millioner tall), det høres kanskje ikke mye ut til å begynne med, men denne økningen betyr at du kan prosessere 9 millioner ekstra tall i samme tidsmengde.

     

    OPTION PROLOGUE:NONE
    OPTION EPILOGUE:NONE
    ALIGN 4
    Multipler PROC
    push ebx
    push esi
    push edi
    push ebp
    mov ebx, 100000000
    mov esi, 3
    mov edi, 5
    xor ebp, ebp
    ALIGN 16
    n1:	mov eax, ebx
    xor edx, edx
    div esi
    test edx, edx
    jz n2
    mov eax, ebx
    xor edx, edx
    div edi
    test edx, edx
    jnz n3
    n2:	add ebp, ebx
    n3:	sub ebx, 1
    jnz n1
    mov eax, ebp
    pop ebp
    pop ebx
    pop edi
    pop esi
    ret
    Multipler ENDP
    OPTION PROLOGUE:PROLOGUEDEF
    OPTION EPILOGUE:EPILOGUEDEF
    

     

    Jeg har nettopp prøve koden din. Det er svært tungvindt å ta det i bruk - det er jo ikke en komplett program, bare en funksjon, og man må skrive resten rundt det. Og siden det er i assembly, er det ikke portable. Men med litt endring (endring av alle push og pop til "r??" istedenfor "e??" former) fikk jeg kjørt den på 64-bit Linux i7-920 systemet mitt. Den kjørte i 0.978s - ikke langt i fra på din PC. Hvilke cpu har du på den?

     

    Jeg kan nevne at koden din ga feil svar, men det kan være bare at jeg ikke klarte å få linket den riktig med en C stub som skriver ut svaret (selv om det ser riktig ut på generte assembly).

     

    Jeg prøvde også med en svært enkel C versjon av programmet (identisk til versjon på Torbjørn sin webside, bare med ekte C og ikke C#). Jeg har ikke brukt noe spesielt optimisering - og den kjørte på 0.254 s.

     

    Det vil si, C versjonen kjørte fire ganger raskere enn assembly versjon. Det er også en bagatell å kompilere og kjøre på hvilket som helst system, var mye raskere å skrive enn assembly versjonen, og er mye klarerer. Som jeg sa tidligere, for alt annet enn hobby bruk og moro, er det langt viktigere å skrive klar og forståelig kode som man vet er riktig, enn å skrive noe som kjører fort. I dette tilfelle er C koden 4 ganger raskere, og langt klarerer.

     

    Jeg lar deg lekke litt mer med assembly'en før jeg fortelle om trikset kompilatoren brukte for å lage raskere kode.

    • Liker 1
  17. Siden vi diskutere hvor ueffektivt C++ er, hva med dette?

     

     

    template <int n>

    int div3or5() { return (((n % 3) == 0) || ((n % 5) == 0)) ? n : 0; }

     

    // Note - we want sum to less than n, so "call" div3or5 with n-1

    template <int n>

    int sumDiv() { return sumDiv<n - 1>() + div3or5<n - 1>(); }

     

    template <>

    int sumDiv<0>() { return 0; }

     

    int test10() {

    return sumDiv<10>();

    }

     

    int test100() {

    return sumDiv<100>();

    }

     

    int test1000() {

    return sumDiv<1000>();

    }

     

     

    Kompilasjon tar litt tid, og kanskje krever at man øker template depth (-ftemplate-depth=1000 med gcc), men genererte koden er jo optimalt.

     

    En versjon som skalere bedre (med lavere template depth) men litt lengre kompileringstider er:

     

    template <int n>

    int div3or5() { return (((n % 3) == 0) || ((n % 5) == 0)) ? n : 0; }

     

    // Sum from a up to but not including b

    template <int a, int b>

    int partSum() {

    if (a == (b - 1)) {

    return div3or5<a>();

    }

    return partSum<a, ((a + b) / 2)>() + partSum<((a + b) / 2), b>();

    }

     

    template <>

    int partSum<0, 0>() { return 0; }

     

    int partTest1000() {

    return partSum<0, 1000>();

    }

    • Liker 1
  18. Igjen synes jeg det er flott at du elsker asm, og det er kjekt å ha et fleksibelt språk å jobbe med, spesielt som en hobby. Vil likevel diskutere litt mer...

     

    2: vi har biblioteker i asm også, alle basiske funksjoner, string funksjoner, matte funksjoner, grafikk funksjoner og alle windows api'er er selvfølgelig tilgjengelige.

     

    3: Alle c biblioteker som eksisterer kan konverteres til assembly include files og brukes på nøyaktig samme måten, uten større problemer. Man bruker ett simpelt verktøy for å konvertere header og include filer.

     

    Så du er villig til å bruke bibloteker, selv de som er skrevet i C. Koden du benytter kan vel da umulig være optimal, utifra det du har sagt tidligere?! Hvis du skal gjøre noe av en viss størrelse vil kjøretiden til programmet ditt i nokså stor grad befinne seg i biblotekene-for å si det sånn. Gjør ikke dette overheadet det nesten meningsløst å bruke asm?

     

    Uansett.., siden du poster litt eksempler så kunne jeg vært veldig interessert i å se en implementasjon av Euler-oppgave 1 implementert i assembly. På bloggen min nå i desember kan du se den oppgaven implementert i 24 ulike språk, men assembly er ikke ett av dem. Er veldig lysten på å se hva som skal til for å skrive ut summen av alle multipler av 3 eller 5.

     

    Tar du utfordringen?

     

    Du har ikke Python på bloggen, så vidt jeg kunne se - den blir svært enkel (selv om det er litt off-topic her):

     

    sum([i for i in range(1000) if (i % 3 == 0) or (i % 5 == 0)])

     

    Skal man lage det generelt :

     

    def sum3or5(m) :

    return sum([i for i in range(m) if (i % 3 == 0) or (i % 5 == 0)])

     

    sum3or5(1000)

     

     

    Jeg regner med at du er klar over at det fins en O(0) algoritme istedenfor O(1) metoden i bloggen din? Det er alltid viktigst å finne den beste algoritmen før man begynner å optimisere...

  19. Det som er litt urovekkende med utviklingen av harddisker for bruk i proffe miljøer er de lange raid rebuild og backup restore tidene en får, siden mengden data per spindel øker raskere enn ytelsen. Har egentlig ikke sett noen god løsing på det. RAID er f.eks på tur ut i de største løsningene som en konsekvens av dette. Backup/restore er kanskje neste offer? bare kjøre aktiv/aktiv med hyppige snapshots og heller feile over/ rulle tilbake snapshots i steden for restore? Blir utfordrende det også.

     

    Det fins flere løsninger - ihvertfall delvis løsninger.

     

    For det første, så er det ikke alle applikasjoner som krever store disker - da bruker man mindre disker (eventuelt bare en del av disken - "short stroking" øker også hastighet på disken). Ellers hvis man trenger både plass og raske rebuilds, bruker man flere disker. Med andre ord, hvis en rebuild tar for langt tid med så store disker, bruker man mindre disker.

     

    Det fins noen RAID løsningene som ikke gjøre initialisering, duplisering, rebuild, parity, eller sjekking på de delene av diskene som ikke er i bruk, slik at disse operasjoner tar ikke lang tid før du har mye data på diskene. Det gjelder ihvertfall ZFS, og er under utvikling i Linux software raid.

     

    Backup og restore er som regel gjort med kopiering av bare endringene i data - da er det ikke så store mengder om gangen. Og ja, men tar zero-kopi snapshots til første nivå backup (som beskytter mot menneskelige feil, som sletting av feil fil) - men man trenger kopiering til et annet system i tillegg (som beskytter mot hardware eller software feil).

     

    Men økende data mengder blir et problem - full kopiering eller rebuild er ikke alltid mulig å unngå. Det er en av grunnene til at RAID5 er blitt sjelden i store arrayer - rebuild tidene er så lange at det er et realistisk sjans å få to disk som feiler. Man bruker heller RAID6, eller hierarkiske RAID (typisk RAID10).

    • Liker 1
  20. Huff, jeg fatter ikke hvorfor du skal slå deg vrang og bry deg om hvordan jeg har skrevet koden. Selv om du sikkert er bitter over manglende ingeniører med kunnskap, så trenger du ikke bekymre deg for at jeg fyller de rekkene enda mer. Jeg er bare ute etter et svar, og forventer ikke at folk skal blande seg inn om jeg kan pensumet eller ikke. Det er mitt problem, og derfor har du ingenting med det å gjøre.

     

    Takk for at du brukte tiden på å kverulere istedenfor å komme med ett anstending svar.

     

    end

     

    Det er et problem for hele samfunnet om studenter jukser - selv om du ikke kommer til å bruke dette faget profesjonelt. Du er jo bare et barn - du tror sikkert at det ikke betyr noe, og til og med at du kan lure folk med kopi-og-lim hjemmelekser. Jeg håper bare at du våkner opp til realitet før du skaper problemer for deg selv og andre.

  21. Nå har ikke jeg en assembler for din arkitektur tilgjengelig så da blir jeg bare nødt til å konkludere med at du har bare tatt med en enkel kalkulasjon, som i seg selv utelukker optimaliseringer som kunne vært gjort rundt denne lille delen. En enkel optimalisering jeg ser i øyeblikket er lasting av data i registrene, som regel tar disse 5 sykluser og kunne vært gjort i forveien for å begrense ventetiden

     

    Disse to instruksjonene:

     

    46 000a 2008 move.l %a0,%d0 |, tmp37

    47 000c 4C01 0800 muls.l %d1,%d0 | x, tmp37

     

    De skaper en halt i prosessoren, og den dobler antall sykluser som trengs enn hvis du hadde shufflet instruksjonene rundt. Bare i denne delen kunne jeg doblet hastigheten.

     

    Her viser du soleklart hvor begrenset du er med assembly. For å ta det fra toppen...

     

    Du har ikke en assembler for denne arkitekturen. Helt riktig - men med koden skrevet i C har man koden klar til <i>alle</i> arkitekturene, og med kode som dette kan man regne med at det er optimal kode i de alle fleste tilfeller. Det er også en hint at du er begrenset til kun x86 assembly - jeg har programmert i ihvertfall 10-12 forskjellige assembly over 25 år, og tør gjette at jeg har en bredere erfaring enn deg selv om du har mye dypere erfaring en den smale felten av x86 assembly under DOS (og eventuelt Windows). Jeg skal ikke påstå at jeg kan alt - men jeg har lært en god del om både assembly, C, og fordeler og ulemper av begge språkene.

     

    Jeg valgte Coldfire fordi den har en assembly som er ganske lett å forstå. En ting du kanskje ikke har fått med deg er at i denne assembly, er rekkefølgen "source, destination". Det eneste litt komplisert instruksjon her er "lea 5(a0, d1*2), a0" som er brukt for å kalkulere "(3*x) + 5" i en instruksjon. Og det viser at kompilatoren har ingen problem med å bruke slike triks, samt Horner's rule. Som sagt, kan kompilatoren dra nytte av konstant verdiene på en måte som assembler programmereren ikke kan dersom koden skal være fleksibel og generelle.

     

    Nest, påstår du at "lasting av data i registrene tar som regel 5 sykluser" - en totalt absurd påstand. Faktisk så variere det voldsomt fra prosessor til prosessor, og er avhengig av minne arkitektur og hvor i minne det fins. Selv om du holder deg til x86 verden, kan en instruksjon som leser data fra stacken tar alt fra 1 syklus (om det ligger i "store buffer" fra før) til over 200 sykluser (om det må hentes fra ekstern minne). I praksis kan man jobber ut fra noen cirka tall på pipeline forsinkelse fra en load (man tar som gitt at det er i nivå 1 cache) - noe som kompilatorene gjør. Og siden det variere etter hvilke type x86 cpu man har, pleier kompilatorene å gi brukeren opsjon til å optimalisere etter bestemte typer x86 - noe som assembly programmererer ikke gjør (med mindre enn at de vil skrive koden mange ganger).

     

    Du har rett at det ofte hjelper å laste inn data på forveien - og kompilatoren hadde gjort det dersom det var noen vits. Men det er lett å se at i dette tilfelle er det ingenting å vinne fordi hver steg er avhengig av den forrige. Det samme gjelder med de to instruksjonene som du mener burde byttes om for å unngå en pipeline stall - da tror jeg du har misforstått rekkefølgen av source og destination register.

     

    Og til slutt påstår du å kunne ha doblet hastigheten - uten at du forstod assembly koden her, uten at du kjente til prosessoren, og uten at du forstod poenget (som var at du ikke kan skrive like raskt kode i assembly og fortsatt være fleksibel med tanke på konstantene).

     

    Jeg liker ikke å klassifisere folk, men dette "jeg kunne doblet hastigheten" påstand er veldig typisk av programmerere som er veldig geiret på assembly og tror at det er alltid den raskeste og mest effektive språk.

     

     

    Jeg skriver alt dette i håpet av at du lære litt. Jeg vil ikke ta vekk gleden av assembly programmering fra deg, og jeg syns at det er svært viktig at programmererer lærer assembly og forstår hvordan det fungere - de blir bedre programmererer av det. Men du blir <i>ikke</i> en bedre utvikler av å tro at assembly er en fornuftig valg av språk til noe mer enn enkelte niche oppgaver, og du blir ikke bedre av å tro at assembly er en god metode til å få raskeste mulig kode.

    • Liker 1
  22. Det er bare snakk om 'loop'-koden som jeg har kopiert. Resten har jeg skrevet selv og jeg forstår det fullt ut. Læreren har ikke snakket om disse tre kommandoene som jeg nevner her, og det er derfor jeg kommer hit for å spørre om hjelp. Er du villig til å svare på spørsmålet mitt nå?

     

    Uansett, takk til dere som postet linker etc. Skal ta en titt på dem senere.

     

    Har du skrevet resten av koden i C? Eller var det til en annen mikrokontroller? Fordi du kan ikke ha skrevet noe som helst i assembly på en PIC uten at du forstår "movlw" og "movwf", og man kommer ikke så veldig langt uten "decfsz".

     

    Kokker det hele ned til at du har skrevet et program i C for en ikke-nevnt mikrokontroller, men du viste ikke hvordan du skulle få en 1-sekund pause, og har dermed kopiert tilfeldig assembly kode til en tilfeldig mikrokontroller fra en tilfeldig webside?

    Det spiller da ingen rolle, jeg vil bare ha movlw, movwf og decfsz forklart inn med teskje. Koden min består mye av bsf, bcf, crlf, call, goto osv.

     

    Nei, det godtar jeg ikke. Selv om det er 15 år siden jeg programmert i PIC assembly, og har muligens glemt et par detaljer, kan jeg ikke uten videre akseptere påstand at du har skrevet selv et enkelt trafikklys program i PIC assembly uten å ha forstått "movlw" og "movwf". Jeg kan forstå at det er mulig å skrive et slikt program der det kun er i pause funksjonen at man bruke disse instruksjoner - men du kan ikke ha lest om og lært om måten PIC cpu'en fungere for å skrive programmet selv uten at du også har vært bort i "movlw" og "movwf".

     

    Du har selv sagt at du ikke har lærebøker eller nettsider som omtale assembly, og at du ikke en gang vet hvilke type assembly det er snakk om. Man skriver ikke assembly kode uten å vite hva slags assembly det er.

     

    Konklusjonen må da være at du ikke har skrevet noen ting av programmet, og har bare kopierte den.

  23. Det er bare snakk om 'loop'-koden som jeg har kopiert. Resten har jeg skrevet selv og jeg forstår det fullt ut. Læreren har ikke snakket om disse tre kommandoene som jeg nevner her, og det er derfor jeg kommer hit for å spørre om hjelp. Er du villig til å svare på spørsmålet mitt nå?

     

    Uansett, takk til dere som postet linker etc. Skal ta en titt på dem senere.

     

    Har du skrevet resten av koden i C? Eller var det til en annen mikrokontroller? Fordi du kan ikke ha skrevet noe som helst i assembly på en PIC uten at du forstår "movlw" og "movwf", og man kommer ikke så veldig langt uten "decfsz".

     

    Kokker det hele ned til at du har skrevet et program i C for en ikke-nevnt mikrokontroller, men du viste ikke hvordan du skulle få en 1-sekund pause, og har dermed kopiert tilfeldig assembly kode til en tilfeldig mikrokontroller fra en tilfeldig webside?

×
×
  • Opprett ny...