Skrivet av Morkul:
Där av att jag angav så väl CPU som minnes cykel ovan.
Och precis som jag försökte påpeka så är antal CPU-cykler en sådan instruktion tar helt irrelevant, framförallt på en RISC där alla instruktioner normalt sätt tar 1 cykel att ta sig genom "dispatch" delen av pipe:en, vilket många anser vara "kostnaden" för att köra instruktionen. Men en annan sak som gör det lite meningslöst att prata klockcykler är att även på simpla CPU:er som ARM Cortex A9 och Intel Atom har man en pipeline på 10-14 steg.
Det kanske är relevant att prata om klockcykler per instruktion på en gammal ARM7 och tidigare där pipe:en bestod av 3 steg, fetch, decode, execute/retire.
Titta på denna rapport som bl.a. tar upp bandbredd och latens mot RAM på en Cortex A9.
Hur ska du överhuvudtaget kunna veta hur lång tid ldrex då tiden för minnesaccessen kan variera mellan 30ns (L1$-hit) till 700ns(måste gå mot RAM)? Då denna rapport kommer från 2009 så kan vi anta att de körde på ganska tidiga A9 utvecklingskort, så klockfrekvensen är nog inte mer än runt 500MHz, så detta motsvarar det en varians på 15-350 klockcykler (sida 17). Blandar man in flera CPU-kärnor går latensen upp än mer (sida 18).
Samma sak gäller genomsnittlig bandbredd, den varierar allt från strax under 1.5GB/s till 200-300MB/s för läsning.
Den som slår en här är vilken brutalt usel cache och RAM design en så pass modern CPU ändå har. Hade väntat mig att moderna x86:er skulle har något lägre latens och ungefär 10 gånger bättre bandbredd, men det räcker inte på långa vägar. Atom har ungefär 10 gånger mer bandbredd mot RAM, teoretiskt är den 4.1GB/s och folk får runt hälften i praktiken (2.6GB/s har uppmätts under Linux).
Tittar man i stället på Sandy Bridge så ligger latensen mot RAM mellan 40-60ns och bandbredden mellan 15GB/s till 20GB/s (beroende på kvalité på RAM, detta är dual-channel). Latensen är en faktor 10 bättre, bandbredden är ju mellan 50-100 gånger bättre...
Skrivet av Morkul:
Jo jag vet tämligen exakt när jag får till tillbaka kontrollen så länge jag vet vad jag gör och ensam har kontrollen på kärnan. Ska jag böra dela kärnan med till exempel systemet så börjar det bli jobbigt dock. Nu har jag turen nog att det OS jag sitter och jobbar mest på är ett OS jag själv har skrivit så jag har tämligen god koll på vad det gör.
Men andra ord så kör du bara system utan OS och med alla interrupt avslagna, för hur kan du annars veta om det data du läste för ett par instruktioner sedan numer befinner sig i L1$, L2$ eller RAM. Det är en skillnad på över en tiopotens i hur lång till en ld instruktion kommer att ta i praktiken.
Skrivet av Morkul:
Här har du en bra poäng och som programmerare gäller det att hålla tungan rätt i munnen. En riktigt bra programmerare räknar noga på hur han ska lägga upp sin programmering för att använda samtliga cyklar han har tillgängliga. Detta är en process som kan vara flera veckors med planering innan man ens börjar att skriva någon kod allt för att kunna para ihop rätt instruktioner med varandra och då menar jag inte endast rent "instruction pairing" utan att även se till att kombinera det bra tillsammans med minnesanvändning. Så målet för en programmerare är ju att få så lite OOE som möjligt men tyvärr så är det ju så att de som inte sitter och programmerar i assembler kan inte göra så mycket åt saken utan kan bara hoppas på att de som gjort kompilatorn har gjort ett bra jobb.
Och de som sitter och programmerar i assembler kan inte HELLER göra något åt saken för det finns INGEN CPU-tillverkar som ger dig tillräckligt med information för att du på klockcykelnivå kan räkna ut hur lång tid saker tar. I ett system med flera CPU-kärnor är det omöjligt även om du visste allt detta.
Detta är något jag fått förklara för mig av personer som jobbar med s.k. MILS (Multiple Independent Level of Security) system där variationer i hur lång tid det tar att köra något kan eventuellt vara en läcka av information.
Det finns lägen när det fortfarande kan vara vettigt att skriva assembler, ett sådant exempel är vissa former av checksummor och andra beräkningar där man kan ha stor nytta av att komma åt carry bit, något som C inte möjliggör. För den checksumma som används av UDP och TCP kan man bara summera 16-bitar i stöten i C, men 32 bitar i assembler tack vare carry-biten som effektivt gör att man får 33 bitar och man måste ha minst en bit mer än storleken på det man processar. Att använda 31-bitar i C är möjligt men blir så komplicerat att det går fortare att bara köra med 16. Samma sak gäller 64-bitar, men då är det 32-bitar i C mot 64-bitar assembler.
Andra saker som kan vara vettigt att skriva i assembler är implementationer av vissa primitiver som mutex, semaphorer och spinlocks. Men även detta kan man skriva i t.ex. gcc genom att använda (icke-standard) extensionen som då gör att samma kod fungerar på alla CPU:er som kompilatorn stödjer i stället för att sitta att hacka en version per CPU.
Rent generellt är det totalt slöseri med tid även för de mest prestandakritiska programmen att skriva i assembler idag. Moderna C kompilatorer har väldigt bra informationen om exakt vilka konstruktioner som är snabba, kolla bara på vilka udda ställen gcc tenderar att använda "lea" i stället för det mycket mer logiska "mov" bara för att "lea" automatiskt ger dig sign-extend. Tricks som xor rx,rx klarar de flesta assemblerprogrammera av, men det gör även moderna kompilatorer.