Lära sig C? Vad är bra material att lära sig ifrån?

Permalänk
Medlem

Lära sig C? Vad är bra material att lära sig ifrån?

Tjena det är så att jag går i tankarna på att lära mig C. Har redan börjat lite smått med lite grunder. Även kodat en del i Python innan så programmering i sig är inte jätte främmande utan jag har väl koll på basics. I alla fall så bedömer jag själv att jag gör det.

Kör numera även Linux på heltid och är nog lite därför programmering har blivit lite av ett hobby projeckt lite då och då och tänkte att eftersom C används väldigt mycket i Linux världen så kanske man skulle ta och lära sig det? Bra eller dålig idé?

Om jag nu ska ta och lära mig C. Vad är bra material att lära sig ifrån? Böcker, internet guider eller kurser? Alla tips är välkommna.

Permalänk
Medlem

@Funlo: Det är inte så att du möjligtvis menar C++ för C är väl ganska ute sen länge annat än i specifika tillämpningar?

Permalänk
Medlem
Skrivet av improwise:

@Funlo: Det är inte så att du möjligtvis menar C++ för C är väl ganska ute sen länge annat än i specifika tillämpningar?

Jag menade spcifikt C. Men kanske jag som fått för mig helt fel. Och att C++ är det som är mer vanligt att använda idag. Även på Linux fronten. Vet i och för sig att C är äldre. Men man kanske ska lära sig C++ istället då. Om vi då omformulerar frågan och utgår ifrån C++ istället.

Permalänk
Medlem
Skrivet av improwise:

@Funlo: Det är inte så att du möjligtvis menar C++ för C är väl ganska ute sen länge annat än i specifika tillämpningar?

C har sina ställen. framförallt är det ett bra språk att lära sig hur pekare fungerar i.. Dessutom kompileras det till assembler som man kan kika på om man vill lära sig det.

Visa signatur

CPU: Ryzen 9 3900x Noctua NH-D14 MOBO: TUF Gaming X570-PLUS GPU: GTX 980 RAM: 32 GB 3200 MHz Chassi: R4 PSU: Corsair AX860 Hörlurar: SteelSeries 840 Mus: Logitech G502 Lightspeed V.v. nämn eller citera mig för att få svar.

Permalänk
Medlem
Skrivet av improwise:

@Funlo: Det är inte så att du möjligtvis menar C++ för C är väl ganska ute sen länge annat än i specifika tillämpningar?

C är verkligen inte ute. Jag har jobbat med programmering i och för Linux/Android i 12 år och använder mest C och ibland lite C++. Det beror på tillämpning och område.

Jag använder "Linux Device Drivers 3rd edition" till och från när jag behöver kolla upp något.

EDIT: Mitt nuvarande projekt är linux drivrutin till pcie gen3 instickskort där vi har följande sw stack:
Generell pcie drivrutin i kernel skriven i C.
Userspace lib (API) skrivet i C.
Userspace drivrutiner skrivna i C (tänk att kernel drivrutinen är generell för nätverkskort på pcie. I userspace drivrutinen gör vi inställningar för olika nätverkskort som funkar med samma kernel drivrutin.)
Applikation i userspace skriven i C++.

Skickades från m.sweclockers.com

Visa signatur

Idag kom Athlon64

Permalänk
Medlem

Det är bra att kunna C även om man normalt pysslar med programmering i högnivåspråk. Framför allt är det bra för att träna på defensiv programmering. Med det menar jag att om man ger sig fan på att lära sig skriva C-program som aldrig kraschar eller läcker minne så kommer antagligen program man skriver i högnivåprogram där en null-referens inte nödvändigtvis dödar ett program också att bli bättre.

Det är också bra att förstå POSIX och systemprogrammering i Linux, vilket med fördel görs i C. Det ökar helt enkelt förståelsen för hur ett OS fungerar.

Själv lärde jag mig först C++ med Skansholms C++ Direkt. Det är möjligt att den boken fortfarande fungerar i uppdaterad version, men det vet jag inget om. Om man ska lära sig C++ idag ska man definitivt se till att använda material om de nyare standarderna.

När man väl kan grunderna i C (eller C++) är manualsidorna en trevlig referens för att börja titta på systemprogramering. Testa "man 2 fork", "man 3 printf" och "man 7 socket" i en Linux-konsoll.

Att titta på kod som andra skrivit är nog det som är mest lärorikt. I Linux kan man ju enkelt ladda ner källkodspaket till de program man har installerade.

Så det finns stor allmänbildningspotential i att kunna C. Och det finns jobb fortfarande och länge till, framför allt inom mer ingenjörsmässiga arbetsområden. Men om man vill satsa på att bli maximalt anställningsbar så skulle jag säga att det är C#, JavaScript och Java som gäller i Sverige. Man får dock mycket gratis i form av syntaxlikheter mellan språken, C är ju moderspråket till samtliga. Det är lätt att lära sig C# eller Java med en bakgrund i C++.

Permalänk
Medlem

@Knightmare: Vilket är skälet att jag skrev "specifika tillämpningar". Har själv inte använt något av språken på många år men har känslan av att C++ är klart vanliga idag iom bättre stöd för OOP.

Edit:
Enligt denna sida har jag dock fel, även om jag tycker det låter otroligt att C skulle vara det näst mest använda språket https://stackify.com/popular-programming-languages-2018/

Permalänk
Medlem
Skrivet av improwise:

@Knightmare: Vilket är skälet att jag skrev "specifika tillämpningar". Har själv inte använt något av språken på många år men har känslan av att C++ är klart vanliga idag iom bättre stöd för OOP.

Edit:
Enligt denna sida har jag dock fel, även om jag tycker det låter otroligt att C skulle vara det näst mest använda språket https://stackify.com/popular-programming-languages-2018/

C används inom många områden. Inbyggda system av alla de slag är ett område där C är stort.
Hela Linux-kärnan liksom många systembibliotek och utility-program till Linux system är skrivna i C.

C++ är nog däremot långsamt utöende idag - även om det fortfarande är populärt och kommer att vara det åtskilliga år framöver. Flera nyare språk (Java, C#, m.fl.) tar långsamt över de områden där C++ varit starkt.

Permalänk
Medlem

@Erik_T: Jo, jag tänkte det samma ang C++, att det finns nyare alterantiv som gör samma sak bättre. Men jag är inte den som inte kan erkänna att jag har fel, och här hade jag uppenbarligen just det. Dock är jag själv jäkligt glad att att slippa C men så rör jag mig mest betydligt högre upp i stacken numera

Permalänk
Medlem
Skrivet av Funlo:

Tjena det är så att jag går i tankarna på att lära mig C. Har redan börjat lite smått med lite grunder. Även kodat en del i Python innan så programmering i sig är inte jätte främmande utan jag har väl koll på basics. I alla fall så bedömer jag själv att jag gör det.

Kör numera även Linux på heltid och är nog lite därför programmering har blivit lite av ett hobby projeckt lite då och då och tänkte att eftersom C används väldigt mycket i Linux världen så kanske man skulle ta och lära sig det? Bra eller dålig idé?

Om jag nu ska ta och lära mig C. Vad är bra material att lära sig ifrån? Böcker, internet guider eller kurser? Alla tips är välkommna.

Vad ska du programmera?

I linux kommer man långt med bash. Vill du ha något med grafik kanske du ska satsa på ett annat språk. C är bra för små program att köra i terminalen.

Visa signatur

Ryzen 9 5950X, 32GB 3600MHz CL16, SN850 500GB SN750 2TB, B550 ROG, 3090 24 GB
Har haft dessa GPUer: Tseng ET6000, Matrox M3D, 3DFX Voodoo 1-3, nVidia Riva 128, TNT, TNT2, Geforce 256 SDR+DDR, Geforce 2mx, 3, GT 8600m, GTX460 SLI, GTX580, GTX670 SLI, 1080 ti, 2080 ti, 3090 AMD Radeon 9200, 4850 CF, 6950@70, 6870 CF, 7850 CF, R9 390, R9 Nano, Vega 64, RX 6800 XT
Lista beg. priser GPUer ESD for dummies

Permalänk
Datavetare

@Funlo: flera anledningar till varför C fortfarande är relevant har redan nämns i tråden.

En sak till som gör C högst relevant än idag är att det i praktiken definierar standarden för hur man kan utöka de flesta högnivåspråk med moduler som kräver väldigt högprestanda och/eller lågnivåtillgång till underliggande system.

Du har ju erfarenhet av Python redan, det är ett exempel på en miljö där man kan skriva utökningar och där dessa utökningar använder sig av ett C-gränssnitt.

Rent tekniskt kan man skriva utökningar i vilket språk som helst, är bara gränssnittet som behöver vara i C. Två välkända exempel som använder denna modell är Tensorflow och PyTorch. Båda dessa är skrivna i C++, exporterar ett C-gränssnitt och används i praktiken nästan uteslutande från Python på applikationsnivå. Fördelen är enkelheten i Python för applikationer och prestandan hos C/C++. Denna modell fungerar väldigt bra när applikationen egentligen inte är mer än ett klister, alla tunga lyft förs i biblioteket.

Frågan du nog bör ställa dig först är vad målet med att lära sig ett till språk är. Handlar det primärt att lära sig ett systemspråk för att skiva krävande applikationer skulle jag säga att C++ (tyvärr, hoppas C++ fort kommer ersättas av Rust) är det självklara valet idag. I princip alla större applikationer, som Chrome/Firefox/Edge, MS Office, Photoshop, spel, eventuella databaser, etc är primärt skrivna i C++.

För C++ är det väldigt viktigt att välja ett material som specifikt beskriver "modern C++", d.v.s. C++11/14/17. C++ var på väg att dö sotdöden, väldigt lite hände där från millennieskiftet fram till att C++11 släpptes. Var bl.a. Microsoft som insåg att deras tunnelseende på .Net var inte helt vettig då majoriteten av deras kassakor är extremt beroende på C++.

Den som leder C++ ISO-kommittén, Herb Sutter, jobbar för Microsoft, men Google och Facebook är också stora C++ användare och har flera personer inom C++ ISO-kommittén.

C är idag det enda realistiska valet för OS-utveckling i de stora populära systemen, men ett allt större fokus på säkerhetsproblem i våra IT-system har fått OS-människorna att allt mer snegla på Rust som framtida språk för OS-utveckling. Är man intresserad att följa den utvecklingen finns t.ex. Microsoft Security Response Center bloggen.

Det som gör Rust så spännande är att det har potential att få lika eller till och med bättre prestanda jämfört med C/C++, det samtidigt som det är ett "säkert" språk som förhindrar typiska C-buggar (minnesläckor, buffertöverskrivningar, etc). Rust är en av extremt få (kan komma på Erlang utöve Rust) språk som också garanterar att program inte har s.k. data-race buggar (bland de värsta buggar man kan ha i mulitrådade program).

Sista språk som kan nämnas i kategorin "systemspråk" är Go. Likt C/C++/Rust är Go riktat specifikt mot extremt prestandakrävande applikationer, Go är primärt optimerat för applikationer där I/O-prestanda är den primära flaskhalsen men det är i nivå med de andra även för beräkningar (men skulle säga att Go i praktiken är något långsammare än C/C++ för just detta). Är extremt lätt att använda C-bibliotek från Go, så även här är det vettigt att också kunna C.

Men Go, likt de flesta moderna språk som Java, C#, JS, etc, inte lämpligt för OS-utveckling då det kräver en runtime (blir ju cirkelberoende att använda en plattform för att utveckla ett OS som är basen för alla runtimes när plattformen själv behöver en runtime...). Så om målet just är att jobba med saker på OS-nivå är det C/C++/Rust som är de relevanta alternativen. Ja, finns OS skrivna i andra språk (bl.a. Java, Go och C#) men då är det nedskalade varianter som skiljer sig på flera fundamentala punkter från vad man normal förväntar sig från dessa.

Om du likt mig vill börja med att läsa om ett nytt språk för att få lite känsla för hur det typiskt används är väl The C-programming language vettig än idag. C har är praktiken ett fryst språk idag. Har släppts en C11 standard, men väldigt få har ens brytt sig om att implementera detta (Windows, Linux och MacOS saknar alla en fullständig C11 implementation, finns en del RTOS som implementerar C11).

Så standarden som gäller för C är C99 eller om ger sig på någon riktigt nischad mikrokontroller kan det vara så illa att man får nöja sig med C89. Men sättet man programmerar C skiljer sig inte vare sig det handlar om C89, C99 eller C11. Det är i stark kontrast till C++11 som på flera sätt fundamental ändrar hur man bäst jobbar med C++.

För C++ beror det än mer hur man bäst lär sig. Många skulle nog rekommendera Bjarnes Stroustrup The C++ programming language. Personligen tycker jag den är en utmärkt referens, även om det är ännu enklare att använda C++-referense och cplusplus.com. Båda dessa innehåller och en rad exempel och tips på hur man bäst använder C++ och dess standardbibliotek.

För Rust är den officiella bästa stället, man har skrivit både en referensmanual och en "by-example" just för att olika personer lär sig på olika sätt.

Go har ett liknande upplägg som Rust, en referensmanual, en "by-examples" och en "best-practices".

Har använt C++ sedan 1992, även undervisning. Använt C i närmare 20 år. Men hoppas att framöver kunna lämna dessa för Rust (OS-utveckling) och Go (allt utom OS-utveckling). C och C++ (som idag är två väldigt olika språk, så gillar inte när man klumpar ihop dessa som C/C++) kommer vara relevanta en lång tid framöver just p.g.a. att de i praktiken är basen för egentligen alla datorsystem idag, så helt vettigt att lära sig dessa även 2019. Men båda börjar visa sin ålder och brister (primärt inom säkerhet, eng. "safety", inte "security", C++11 är en klart fall framåt med många brister kvarstår) och nu finns faktiskt realistiska alternativ!

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem

@Yoshman:

Jag är lite osäker på detta

Citat:

Det som gör Rust så spännande är att det har potential att få lika eller till och med bättre prestanda jämfört med C/C++,

Rust har någon form av overhead jämfört med C/C++. Vilken typ av tillämpning är det vi pratar om som gör att Rust har bättre prestanda? Ett exempel på att Rust är snäppet långsammare är detta arbete, en sammanfattning finns att läsa här.

Jag håller med om att Rust kommer att ta ännu mera mark i framtiden. Gillar sättet Rust mer eller mindre "tvingar" en att skriva säker kod med väldigt liten prestandaförlust jämfört med C/C++.

Permalänk
Medlem
Skrivet av Alotiat:

@Yoshman:

Jag är lite osäker på detta
Rust har någon form av overhead jämfört med C/C++. Vilken typ av tillämpning är det vi pratar om som gör att Rust har bättre prestanda? Ett exempel på att Rust är snäppet långsammare är detta arbete, en sammanfattning finns att läsa här.

Jag håller med om att Rust kommer att ta ännu mera mark i framtiden. Gillar sättet Rust mer eller mindre "tvingar" en att skriva säker kod med väldigt liten prestandaförlust jämfört med C/C++.

Rust skryter ofta med att de har sk. "zero-cost abstractions" med vilket menas att den smarta kompilatorn tillsammans med det starka typsystemet oftast kan garantera att till synes hög-abstrakta uttryck kompileras till effektivast möjliga maskinkod. Rusts iteratorer är ett sådant exempel. I beteende och bruk är de väldigt hög-nivå och enkla att arbeta med, men koden som genereras är ekvivalent med en handskriven for-loop i C. Självklart blir det overhead ibland, men oftast inte, och ibland kan till och med mer effektiv kod än C/C++ genereras just på grund av att fler delar av typsystemet är statiska, och smartare optimeringar kan göras.

Detta fall av smartare kodgenerering är dock inte något som gör Rust märkbart snabbare än de andra språken, men det finns en annan aspekt som bidrar till att Rust-kod stundvis kan utprestera iallafall C väldigt tydligt. Detta är inte på grund av något speciellt med Rust i sig egentligen, utan bara det faktum att Rust har parametrisk polymorfism / generics, medan C inte har det. Parametrisk polymorfism gör det otroligt mycket mindre smärtsamt att använda effektiva, generiska datastrukturer i Rust än i C. Vissa av de mer effektiva datastrukturerna är så bökiga att använda på effektivast möjliga sätt i C, att många väljer att använda mindre effektiva datastrukturer eller att använda void-pekare för polymorfism, vilket medför onödig minnes-indirektion och är mindre cache-vänligt. Det är alltså tekniskt sätt skillnaden i vilka datastrukturer som används, snarare än hur bra kompilatorn är, som gör den stora skillnaden. Jag tycker dock att man kan se det som en fördel med Rust som språk att programmerare inte skräms bort från att använda effektiva datastrukturer. Här är en blogpost som går in lite på detta: http://dtrace.org/blogs/bmc/2018/09/28/the-relative-performan....

Visa signatur

Arbets- / Spelstation: Arch Linux - Ryzen 5 3600 - RX 7900 XT - 32G DDR4
Server: Arch Linux - Core i5-10400F - 16G DDR4

Permalänk
Datavetare
Skrivet av Alotiat:

@Yoshman:

Jag är lite osäker på detta
Rust har någon form av overhead jämfört med C/C++. Vilken typ av tillämpning är det vi pratar om som gör att Rust har bättre prestanda? Ett exempel på att Rust är snäppet långsammare är detta arbete, en sammanfattning finns att läsa här.

Jag håller med om att Rust kommer att ta ännu mera mark i framtiden. Gillar sättet Rust mer eller mindre "tvingar" en att skriva säker kod med väldigt liten prestandaförlust jämfört med C/C++.

En något som med tiden har blivit en rejäl designmiss i C/C++ (och som helt eller delvis tyvärr kopierat av många moderna språk) är dess alias-regler. D.v.s. två argument som skickas som pekare eller referens till en funktion kan helt eller delvis referera till samma minnesregion. Resultat av detta är att det nästan är omöjligt för kompilatorn att på ett helt 100 % säkert sätt utnyttja SIMD-instruktioner (SSE/AVX på x86, NEON på ARM) för att optimera koden.

Rust har överhuvudtaget inte detta problem då semantiken i språket garanterar att det vid varje specifikt kontext bara kan finnas en enda referens/pekare med skrivmöjlighet till ett specifikt minnesområde. Det kan finnas flera referenser/pekare för läsning, men det är inte ett problem.

Redan idag ser man att Rust/LLVM är väldigt aggressiv med att lägga ut SIMD-instruktioner när man t.ex. jobbar med vektorer/arrayer.

Men finns också småvinster på lite oväntade håll

int foo(const int *a, int *b) { if (*a < 10) { *b = bar(a, b); } if (*a >= 10) { // do something } }

Här känns det ju som en uppenbar optimering att läsa in det a pekar på i ett register och återanvända eller till och med förutsätta att enda körs första if-kroppen eller så körs den andra. I Rust är den optimeringen helt OK, i C och C++ är den inte det (inte heller i C#/Java om vi i stället byter ut pekare mot referens till objekt).

Orsaken är att anropet till bar(a, b) kan ändra tillståndet på det a pekar på. Endera för att a och b pekar på samma sak eller helt enkelt för att bar(a, b) ändrar på det a pekar på via någon annan mekanisk (just det senare fallet är något många funktionella språk kan förutsätta inte händer).

Angående länken. Vet du vad DPDK är? Det finns självklart fall där C kan bli snabbare om man i C implementationen helt sonika väljer att överhuvudtaget inte verifiera vissa saker som alla de andra språken gör. Trots att C fallet totalt utelämnar verifiering av att man inte läser/skriver utanför allokerat minne i ett fall som till största del enbart består i att just läsa/skriva till minnesbuffertar så presterar det väldigt nära C (och bättre än något annat språk som testas).

Det är möjligt att ignorera dessa tester även i Rust i en specifik del av koden. Kan tyckas att man inte vunnit något över C i det läget, men poängen då är att man enbart skulle göra detta på små specifika områden som man måste lägga massor med resurser på att manuellt (d.v.s. med människor) verifiera att koden är korrekt.

Omvänt, även om man försöker lägga in samma tester i C blir det ändå inte alls samma sak. Off-by-one och data-race buggar i C kommer fortfarande kompilera och även om man försöker testa för dem, vilket inte är fallet i Rust.

DPDK är speciellt då det i praktiken är ett sätt att lyfta in direktaccess till nätverkskort i "user-space". Om/när vi börjar skriva OS i Rust så kommer just drivrutiner vara ett område där man måste kliva in Rust-världens "osäkra" delar. Går helt enkelt inte att göra vissa saker som måste göras i en drivrutin utan kontrollen som finns i ASM/C/C++ (vilket är en primär orsak till varför OS fortfarande skrivs primärt i C). Men fördelen mot C är att dessa områden är begränsade, så när det dyker upp en bugg behöver man (ignorerat kompilatorbuggar, men de är rätt sällan där felet ligger i praktiken) leta i de delar av koden som är markerade "unsafe" (nyckelordet i Rust för att gå in i C/C++ läget med avseende på kontroll/säkerhet).

Så för att förtydliga: Rust kan vara snabbare än C i kod som utför beräkningar och transformer på data, d.v.s. majoriteten av alla kod som skrivs. I HW-nära kod där man primärt gör trivial minnesaccess får man göra avvägningen mellan värdet i något högre prestanda (C-prestanda) utan säkerhet eller något lägre prestanda med säkerhet som inte kan uppnås i C.

C++ är närmare C än Rust här, men även C++ kan vara snabbare än C tack vare templates och allt mer funktioner som är s.k. "constexpr" (logik som evalueras vid kompilering). Rätt använt fångar också C++ vissa av de typiska C-buggarna, dock tillkommer endera en kostnad vid kompilering eller vid runtime (Rust har samma möjligheter som C++ här).

Angående det sista: det riktigt coola är just att man hittar data-race vid kompilering! Det finns några få saker man kan göra i C/C++/C#/Java (och säker ett par andra) som rent tekniskt är OK, men som inte kommer kompilera i Rust (men är oftast relativt enkelt att modifiera logiken så det även fungerar i Rust). Det är ett rätt litet pris att betala för att kunna hitta data-race och ha noll "undefined behavior" i all kod som kompilerar och inte är markerad "unsafe".

Slutligen: man måste komma ihåg att Rust deklarerades "stabilt" så sent som mitten av 2015. Jobbet med att riktigt få ut max ur de möjligheter som finns på kompilatornivå lär inte realiseras än på ett tag. Ge det till 2025, ett decennium brukar vara ungefär vad ny teknik behöver för att både visa sin riktiga potential samt även börjar visa eventuella brister.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Skrivet av Yoshman:

En något som med tiden har blivit en rejäl designmiss i C/C++ (och som helt eller delvis tyvärr kopierat av många moderna språk) är dess alias-regler. D.v.s. två argument som skickas som pekare eller referens till en funktion kan helt eller delvis referera till samma minnesregion. Resultat av detta är att det nästan är omöjligt för kompilatorn att på ett helt 100 % säkert sätt utnyttja SIMD-instruktioner (SSE/AVX på x86, NEON på ARM) för att optimera koden.

Aliasing ställer till det när man skall optimera koden, men det är inget jätteproblem vid just vektorisering. Jämfört med hur mycket analys som skall till för att man skall kunna avgöra om loopen är vektoriserbar eller inte, så är marginellt med extrajobb att lägga ut kod som kollar om du har aliasing eller inte. Den kollen är i praktiken försumbar jämfört med tiden du spenderar i loopen. Man behöver normalt ändå en cleanup-loop för att ta hand om de loop-varven som inte går jämnt upp med vektorlängden och har man aliasing hoppar man direkt till den.

Men, ja, det vore lättare och effektivare om man inte behövde ta hänsyn till aliasing.

Permalänk
Medlem
Skrivet av Yoshman:

En något som med tiden har blivit en rejäl designmiss i C/C++ (och som helt eller delvis tyvärr kopierat av många moderna språk) är dess alias-regler. D.v.s. två argument som skickas som pekare eller referens till en funktion kan helt eller delvis referera till samma minnesregion. Resultat av detta är att det nästan är omöjligt för kompilatorn att på ett helt 100 % säkert sätt utnyttja SIMD-instruktioner (SSE/AVX på x86, NEON på ARM) för att optimera koden.

Rust har överhuvudtaget inte detta problem då semantiken i språket garanterar att det vid varje specifikt kontext bara kan finnas en enda referens/pekare med skrivmöjlighet till ett specifikt minnesområde. Det kan finnas flera referenser/pekare för läsning, men det är inte ett problem.

Redan idag ser man att Rust/LLVM är väldigt aggressiv med att lägga ut SIMD-instruktioner när man t.ex. jobbar med vektorer/arrayer.

Men finns också småvinster på lite oväntade håll

int foo(const int *a, int *b) { if (*a < 10) { *b = bar(a, b); } if (*a >= 10) { // do something } }

Här känns det ju som en uppenbar optimering att läsa in det a pekar på i ett register och återanvända eller till och med förutsätta att enda körs första if-kroppen eller så körs den andra. I Rust är den optimeringen helt OK, i C och C++ är den inte det (inte heller i C#/Java om vi i stället byter ut pekare mot referens till objekt).

Orsaken är att anropet till bar(a, b) kan ändra tillståndet på det a pekar på. Endera för att a och b pekar på samma sak eller helt enkelt för att bar(a, b) ändrar på det a pekar på via någon annan mekanisk (just det senare fallet är något många funktionella språk kan förutsätta inte händer).

Angående länken. Vet du vad DPDK är? Det finns självklart fall där C kan bli snabbare om man i C implementationen helt sonika väljer att överhuvudtaget inte verifiera vissa saker som alla de andra språken gör. Trots att C fallet totalt utelämnar verifiering av att man inte läser/skriver utanför allokerat minne i ett fall som till största del enbart består i att just läsa/skriva till minnesbuffertar så presterar det väldigt nära C (och bättre än något annat språk som testas).

Det är möjligt att ignorera dessa tester även i Rust i en specifik del av koden. Kan tyckas att man inte vunnit något över C i det läget, men poängen då är att man enbart skulle göra detta på små specifika områden som man måste lägga massor med resurser på att manuellt (d.v.s. med människor) verifiera att koden är korrekt.

Omvänt, även om man försöker lägga in samma tester i C blir det ändå inte alls samma sak. Off-by-one och data-race buggar i C kommer fortfarande kompilera och även om man försöker testa för dem, vilket inte är fallet i Rust.

DPDK är speciellt då det i praktiken är ett sätt att lyfta in direktaccess till nätverkskort i "user-space". Om/när vi börjar skriva OS i Rust så kommer just drivrutiner vara ett område där man måste kliva in Rust-världens "osäkra" delar. Går helt enkelt inte att göra vissa saker som måste göras i en drivrutin utan kontrollen som finns i ASM/C/C++ (vilket är en primär orsak till varför OS fortfarande skrivs primärt i C). Men fördelen mot C är att dessa områden är begränsade, så när det dyker upp en bugg behöver man (ignorerat kompilatorbuggar, men de är rätt sällan där felet ligger i praktiken) leta i de delar av koden som är markerade "unsafe" (nyckelordet i Rust för att gå in i C/C++ läget med avseende på kontroll/säkerhet).

Så för att förtydliga: Rust kan vara snabbare än C i kod som utför beräkningar och transformer på data, d.v.s. majoriteten av alla kod som skrivs. I HW-nära kod där man primärt gör trivial minnesaccess får man göra avvägningen mellan värdet i något högre prestanda (C-prestanda) utan säkerhet eller något lägre prestanda med säkerhet som inte kan uppnås i C.

C++ är närmare C än Rust här, men även C++ kan vara snabbare än C tack vare templates och allt mer funktioner som är s.k. "constexpr" (logik som evalueras vid kompilering). Rätt använt fångar också C++ vissa av de typiska C-buggarna, dock tillkommer endera en kostnad vid kompilering eller vid runtime (Rust har samma möjligheter som C++ här).

Angående det sista: det riktigt coola är just att man hittar data-race vid kompilering! Det finns några få saker man kan göra i C/C++/C#/Java (och säker ett par andra) som rent tekniskt är OK, men som inte kommer kompilera i Rust (men är oftast relativt enkelt att modifiera logiken så det även fungerar i Rust). Det är ett rätt litet pris att betala för att kunna hitta data-race och ha noll "undefined behavior" i all kod som kompilerar och inte är markerad "unsafe".

Slutligen: man måste komma ihåg att Rust deklarerades "stabilt" så sent som mitten av 2015. Jobbet med att riktigt få ut max ur de möjligheter som finns på kompilatornivå lär inte realiseras än på ett tag. Ge det till 2025, ett decennium brukar vara ungefär vad ny teknik behöver för att både visa sin riktiga potential samt även börjar visa eventuella brister.

Det "går" att hjälpa kompilatorn med det första problemet du tar upp. Kanske inte exakt samma sak, men man kan använda restrict för att få kompilatorn att optimera bättre för fallet bar(a,b), se exempel restrict keyword. EDIT: Eller... nej detta kanske inte alls hjälper till i fallet du tar upp.

Nope jag känner inte till DPDK, aldrig riktigt varit insatt i nätverksramverk. Men det var onekligen intressant läsning! Visste inte att Intel hade startat upp det.

Jag jobbar mest med beräkningar/transformation av data och snubblade över Rust vs C av en slump. Men du menar alltså att Rust kan optimeras aggressivt för beräkningsproblem? Säg att jag har ett problem som handlar om att utföra stora matrismultiplikationer, hur skulle Rust stå sig mot t.ex. C++ som jag använder nu? För där finns BLAS biblioteket, exempelvis.

Ja jag håller med om att det tar tid tills det mognat. Vi får vänta och se var det hela landar.

EDIT: Hittade detta blogginlägg som tar upp SIMD exempel med Rust. Även BLAS finns som wrapper men vet ej hur det presterar blas-rust.

Permalänk
Datavetare
Skrivet av Alotiat:

Det "går" att hjälpa kompilatorn med det första problemet du tar upp. Kanske inte exakt samma sak, men man kan använda restrict för att få kompilatorn att optimera bättre för fallet bar(a,b), se exempel restrict keyword. EDIT: Eller... nej detta kanske inte alls hjälper till i fallet du tar upp.

Nope jag känner inte till DPDK, aldrig riktigt varit insatt i nätverksramverk. Men det var onekligen intressant läsning! Visste inte att Intel hade startat upp det.

Jag jobbar mest med beräkningar/transformation av data och snubblade över Rust vs C av en slump. Men du menar alltså att Rust kan optimeras aggressivt för beräkningsproblem? Säg att jag har ett problem som handlar om att utföra stora matrismultiplikationer, hur skulle Rust stå sig mot t.ex. C++ som jag använder nu? För där finns BLAS biblioteket, exempelvis.

Ja jag håller med om att det tar tid tills det mognat. Vi får vänta och se var det hela landar.

EDIT: Hittade detta blogginlägg som tar upp SIMD exempel med Rust. Även BLAS finns som wrapper men vet ej hur det presterar blas-rust.

"restrict" fungerar bra i C99 och senare, men det är faktiskt inte en del av C++-standarden. I Rust är det som om alla pekare/referenser med skrivmöjlighet är markerade "restrict".

Vad det gäller prestandavinst från vettiga alias-regler måste man ha rimliga förväntningar. Inte så att Rust kommer bli heltalsfaktorer snabbare, förväntningen ska i stället ligga på: det kan bli några enstaka procent snabbare i vissa lägen. Det som är intressant här är ju att Rust kan nå samma prestandanivå som C och C++, de två språk som idag är självklara val i lägen där prestanda/effektivitet betyder allt.

Vidare ska man ha realistiska förväntningar även på hur mycket auto-vektorisering som en kompilator faktiskt kommer kunna utföra, det oavsett alias-regler. De stora programspråket beskrivs ju uppgiften helt skalärt, innan kompilatorerna får en AI på nivå av en bra programmerare kommer de inte kunna göra underverk här.

För att en kompilator ska riktigt ska kunna utnyttja SIMD måste koden beskriva problemet på ett SIMD "snällt" sätt. Nvidias CUDA är ett lysande exempel på hur en lite annan beskrivning gör det relativt enkelt för kompilatorn att utnyttja sådana exekveringsenheter.

OpenCL kan användas både för CPU och GPU, men det är primärt designat för GPU. Bästa miljön jag känner till för att generera riktigt bra SIMD-kod är ISPC. I stället för att handjaga SSE, AVX eller AVX512 skriver man sin dataparallella kod i en variant av C (som är väldigt modellerad efter CUDA/OpenCL men bättre anpassad för SIMD på CPU). Är sedan möjligt att bygga för en specifik SSE- eller AVX-version alt. helt sonika bygga för t.ex. SSE2, SSE4, AVX2 och AVX512 och välja nivå runtime baserat på vad den CPU man sitter på fixar.

ISPC stödjer numera också NEON (SIMD på ARM), både för 32-bitars och 64-bitars ARM.

Trådens titel är "Lära sig C?...". Att saker som CUDA och ISPC finns är en annan orsak varför C trots allt är relevant 2019. Har man dataparallella problem kan dessa nästan alltid gå att lösa heltalsfaktorer (upp mot tiopotenser när GPGPU passa väl) snabbare med SIMD instruktioner jämfört med "vanliga" skalära instruktioner.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem

Jag hade rekommenderat "Programming from the ground up" av Jonathan Bartlett (finns som gratis PDF på nätet)

och efter det "C Programming Language" av Brian W Kernighan och Dennis Ritchie.

Visa signatur

Huvudriggen är en Gigabyte Aorus Xtreme | 128gb DDR5 6000 | Ryzen 7950X | 3080Ti
Utöver det är det för många datorer, boxar och servar för att lista :P

Permalänk

C-gurun på mitt universitet rekommenderade alltid "Learn C the hard way".