C++ och dess framtid att programmera minnessäkert - Hur går utvecklingen?

Permalänk
Medlem
Skrivet av klk:

Ta följande exempel, skulle du kunna berätta hur kompilatorn/processorn gör om följande kod?
Vad tror du ligger bakom respektive design eller spelar det någon roll

#include <string> struct StructWithString { int int_val; double double_val; std::string string_val; }; struct StructWithBuffer { int int_val; double double_val; char buffer[32]; };

Det där är inte kod - som i instruktioner till datorn.
Det där deklarationer av ett par structar. De gör kompilatorn ingenting med - deklarationer i sig översätts ju inte av kompilatorn till någonting alls.

Permalänk
Medlem
Skrivet av klk:

Massor.

Om du lägger ner tid och optimerar en klass som klarar av samma jobb som kanske annars hade behövt 20 olika, då vinner du dels på att klassen är bättre anpassad för återanvändning, mindre buggar och investerad tid är inte bortkastad om det behövs ändringar och klassen klarar det.

Optimera kod tar tid och koden kan se lite lustig ut om man inte är van. Skrivs bara nya klasser med hårdkodad logik för just den uppgiften finns inte tid för optimeringar, även om den tiden fanns blir koden så komplex att det är omöjligt att hantera den.

1 optimerad klass är enklare än att optimera 20

Antalet klasser (om man nu envisas med objektorienterad programmering) skall inte vara baserat på hur man tror sig kunna optimera dem.
De klasser man har (och därmed antalet) skall baseras på vad som gör programstrukturen klar och redig och programmet begripligt.

De flesta optimeringar bör inte göras förrän efter att man konstaterat
a) Att optimeringar faktiskt behövs
b) Vad som tar tid, så man inte lägger tid på att optimera fel saker

Permalänk
Medlem
Skrivet av Erik_T:

Det där är inte kod - som i instruktioner till datorn.
Det där deklarationer av ett par structar. De gör kompilatorn ingenting med - deklarationer i sig översätts ju inte av kompilatorn till någonting alls.

Givetvis men min tanke var att det skulle vara självförklarande för man bygger logik kring data, koden som skrivs arbetar på data. Och datat avgör snabbheten. Så vad processorn måste göra är att bearbeta data annorlunda jämfört med vad som skrivits i koden om koden visat sig vara långsammare.

Då är de här två exemplen enklast möjliga för att beskriva om någon skulle vilja förklara det hela praktiskt. För det är ju gott om teoretiska experter i tråden

Permalänk
Medlem
Skrivet av Erik_T:

a) Att optimeringar faktiskt behövs
b) Vad som tar tid, så man inte lägger tid på att optimera fel saker

Går det att exemplifiera det, kan du ta något exempel på hur det fungerar rent praktiskt

Permalänk
Medlem
Skrivet av klk:

Givetvis men min tanke var att det skulle vara självförklarande för man bygger logik kring data, koden som skrivs arbetar på data. Och datat avgör snabbheten. Så vad processorn måste göra är att bearbeta data annorlunda jämfört med vad som skrivits i koden om koden visat sig vara långsammare.

Då är de här två exemplen enklast möjliga för att beskriva om någon skulle vilja förklara det hela praktiskt. För det är ju gott om teoretiska experter i tråden

Vad kompilatorer och processorer gör är ju baserat på den kod och data man matar in.
Det du presenterade var inte ens data - det var bara ett par deklarationer.

Utan kod så kan all data bara ignoreras av processorn. Är inget mer att göra eller förklara.

Permalänk
Medlem
Skrivet av Erik_T:

Vad kompilatorer och processorer gör är ju baserat på den kod och data man matar in.
Det du presenterade var inte ens data - det var bara ett par deklarationer.

Utan kod så kan all data bara ignoreras av processorn. Är inget mer att göra eller förklara.

När jag granskat genererad assemblerkod så är sådant här helt avgörande.

I exemplet visas två olika structar, de hanterar nästan samma data bara att en struct har en string medan den andra har en buffer för texten. självklart är där en string mer "användbart" och kräver mindre kod, och det är den lösning som i princip alltid används i GC kod.

Så varför då använda en buffer?
Andra structen som endast lagrar text internt och då har en maxgräns har fördelen att det bara är block, inga destruktorer eller konstruktorer körs och minnet ligger väl placerat.

Samma med den postade tabell klassen, ett enda stort minnesblock.Låt säga att du haft 1000 rader i en tabell och det ligger värden på varje rad som är objekt. radera 1000 rader och en hel hög kod måste köras för det som rensar upp.

Kod som refaktorerats med den klassen har sett betydande prestandaförbättringar i kombination med mindre domänspecifik kodmassa.

Så fort medlem i klass läggs till som hanterar sitt eget minnesblock blir klassen långsammare, har inte sett att processorer eller kompilatorer kan fixa till det

Permalänk
Medlem
Skrivet av klk:

Går det att exemplifiera det, kan du ta något exempel på hur det fungerar rent praktiskt

Nu pratar vi nybörjarnivå, men okej då.

Antag att vi har en funktion foo() som du tror skulle kunna göras snabbare om du lade ett par dagars jobb på det. Är det värt att göra?
Närmare undersökning visar att foo() tar 0.01 sekunder att köra, och körs endast en enda gång - nämligen under uppstarten av programmet.
Så är det värt att försöka minska tiden det tar för programmet att start med, i bästa fall, 0.01 sekunder. I de allra flesta fall är svaret nej.

Om man har obegränsat med tid så kan man ju försöka optimera varenda liten bit av den kod man skriver, men det har man vanligtvis inte.

Så man måste prioritera vad man använder sin tid till.

Man skall inte optimera saker som inte behöver optimeras - är koden redan tillräckligt snabb, och om den inte använder för mycket minne eller andra resurser, ja då behöver den inte optimeras utan man kan ägna sin tid åt viktigare saker.

Om koden behöver optimeras, då får man börja med att mäta och profilera för att ta reda på var i koden optimeringar skulle göra mest nytta.
Var i koden spenderar processorn mest tid? Om programmet använder för mycket minne, vad används minnet till? Och så vidare.

Vissa optimeringar kan man göra redan innan man skriver kod, nämligen vid val av algoritmer och datastrukturer. Man kan kanske inte göra ett optimalt val redan då, men man kan undvika många dåliga val.
Behöver man till exempel sortera en större mängd tal så är det nästan alltid bättre att använda quicksort än att använda bubblesort. Kan finnas bättre val, men vi vet att bubblesort är en av de sämre sorteringsalgoritmerna, och att quicksort är en relativt bra algoritm.
Har man data som man ofta behöver söka i, så är det lätt att inse att en länkad lista är ett dåligt val för hur man lagrar datat, så att man bör välja en lämpligare datastruktur.
Och så vidare.

Allt det här är sånt som du redan borde kunna. Det är sånt som alla som programmerar yrkesmässigt bör känna till.

Permalänk
Medlem
Skrivet av Erik_T:

Allt det här är sånt som du redan borde kunna. Det är sånt som alla som programmerar yrkesmässigt bör känna till.

Och det gör jag så kan du dra versionen för när du faktiskt börjar optimera, hur gör du då?

Beskriv hur du skriver om kod för att den skall gå snabbare

Skrivet av Erik_T:

Vissa optimeringar kan man göra redan innan man skriver kod, nämligen vid val av algoritmer och datastrukturer. Man kan kanske inte göra ett optimalt val redan då, men man kan undvika många dåliga val.
Behöver man till exempel sortera en större mängd tal så är det nästan alltid bättre att använda quicksort än att använda bubblesort. Kan finnas bättre val, men vi vet att bubblesort är en av de sämre sorteringsalgoritmerna, och att quicksort är en relativt bra algoritm.
Har man data som man ofta behöver söka i, så är det lätt att inse att en länkad lista är ett dåligt val för hur man lagrar datat, så att man bör välja en lämpligare datastruktur.
Och så vidare.

Här väljer du färdig kod. Olika typer kod som följer med.
Jag menar när du skriver koden

Permalänk
Medlem
Skrivet av klk:

När jag granskat genererad assemblerkod så är sådant här helt avgörande.

I exemplet visas två olika structar, de hanterar nästan samma data bara att en struct har en string medan den andra har en buffer för texten. självklart är där en string mer "användbart" och kräver mindre kod, och det är den lösning som i princip alltid används i GC kod.

Så varför då använda en buffer?
Andra structen som endast lagrar text internt och då har en maxgräns har fördelen att det bara är block, inga destruktorer eller konstruktorer körs och minnet ligger väl placerat.

Samma med den postade tabell klassen, ett enda stort minnesblock.Låt säga att du haft 1000 rader i en tabell och det ligger värden på varje rad som är objekt. radera 1000 rader och en hel hög kod måste köras för det som rensar upp.

Kod som refaktorerats med den klassen har sett betydande prestandaförbättringar i kombination med mindre domänspecifik kodmassa.

Så fort medlem i klass läggs till som hanterar sitt eget minnesblock blir klassen långsammare, har inte sett att processorer eller kompilatorer kan fixa till det

Nu pratar du om hur structarna fråga används - vilket inte går att avgöra bara genom att titta på structarna själva.

Det är stor skillnad på om en klass instantieras bara ett par gånger under programmets livstid eller om det görs tiotusentals eller fler gånger.
I det förra fallet så spelar är exekveringstiden för konstruktor/destruktor oftast irrelevant. I det senare fallet kan det spela stor roll - eller inte göra någon märkbar skillnad.

Permalänk
Medlem
Skrivet av klk:

Och det gör jag så kan du dra versionen för när du faktiskt börjar optimera, hur gör du då?

Beskriv hur du skriver om kod för att den skall gå snabbare

Det beror ju på hur koden ser ut till att börja med! Så det kan jag inte beskriva i generella termer.
Att ta fram ett icke-trivialt exempel som demonstration skulle ta mycket mer tid och energi än jag är villig att spendera på dig.

Permalänk
Medlem
Skrivet av Erik_T:

Det beror ju på hur koden ser ut till att börja med! Så det kan jag inte beskriva i generella termer.
Att ta fram ett icke-trivialt exempel som demonstration skulle ta mycket mer tid och energi än jag är villig att spendera på dig.

Men om du ser mina två structar och vet skillnaden så är det ett vanligt exempel på hur kod optimeras.
Sådant här bör RUST utvecklare kunna i och med att det där finns så mycket regler och de då blir vana vid hur minnet behöver hanteras.

Annat som att försöka anpassa klassers storlek efter cache lines (64 byte) eller kanske använda flaggor i ett större tal istället för enskilda medlemmar som bara håller state

Skall man köra flera trådar och dra nytta av deras extra hastighet utan att tänka på minimalt med synkronisering så bli inte förvånad om det knappt blir någon förbättring o.s.v

Permalänk
Medlem
Skrivet av Erik_T:

I det förra fallet så spelar är exekveringstiden för konstruktor/destruktor oftast irrelevant. I det senare fallet kan det spela stor roll - eller inte göra någon märkbar skillnad.

Men den typen av kod behöver troligen inte optimeras alls, just nu pratar vi väl kod som drar nytta av mer hastighet.

Annan kod, där kan du lika gärna använda python ;), 1000 gånger långsammare men vem bryr sig om det tar en 0.001 sekund eller en sekund

Permalänk
Datavetare
Skrivet av klk:

Ta följande exempel, skulle du kunna berätta hur kompilatorn/processorn gör om följande kod?
Vad tror du ligger bakom respektive design eller spelar det någon roll

#include <string> struct StructWithString { int int_val; double double_val; std::string string_val; }; struct StructWithBuffer { int int_val; double double_val; char buffer[32]; };

Vad är din poäng här? För gissar att du vill få det till att det senare fallet med en buffer (den som hanterar strängar så där med C++ 2025 borde få gå någon form av kurs...) skulle vara snabbare.

Låt oss skriva en benchmark

#include <string>

struct StructWithString {
int int_val;
double double_val;
std::string string_val;
};

struct StructWithBuffer {
int int_val;
double double_val;
char buffer[32];
};

Write a pair of benchmarks using google benchmark that populates a std::vector<> with each struct, fill them with random data where the random string is characters from "[a-z][0-9]" set with strings from 10 to 20 characters in length. Then sort each array in ascending string length, if same length sort in lexicographical order

#include <benchmark/benchmark.h> #include <algorithm> #include <array> #include <cstddef> #include <cstdint> #include <cstring> #include <random> #include <string> #include <string_view> #include <vector> struct StructWithString { int int_val; double double_val; std::string string_val; }; struct StructWithBuffer { int int_val; double double_val; char buffer[32]; // NUL-terminated }; static inline char random_ch(std::mt19937_64& rng) { static constexpr std::string_view alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; std::uniform_int_distribution<int> dist(0, static_cast<int>(alphabet.size() - 1)); return alphabet[static_cast<std::size_t>(dist(rng))]; } static inline std::string random_token(std::mt19937_64& rng) { std::uniform_int_distribution<int> len_dist(10, 20); int len = len_dist(rng); std::string s; s.resize(static_cast<std::size_t>(len)); for (int i = 0; i < len; ++i) s[static_cast<std::size_t>(i)] = random_ch(rng); return s; } static inline void fill_buffer_from(std::mt19937_64& rng, char (&buf)[32]) { std::string s = random_token(rng); std::memset(buf, 0, sizeof(buf)); std::memcpy(buf, s.data(), s.size()); buf[s.size()] = '\0'; } static inline std::size_t buf_len(const StructWithBuffer& x) { return ::strnlen(x.buffer, sizeof x.buffer); } static inline bool less_string(const StructWithString& a, const StructWithString& b) { const auto la = a.string_val.size(); const auto lb = b.string_val.size(); if (la != lb) return la < lb; return a.string_val < b.string_val; } static inline bool less_buffer(const StructWithBuffer& a, const StructWithBuffer& b) { const auto la = buf_len(a); const auto lb = buf_len(b); if (la != lb) return la < lb; // same length: lexicographic compare of exactly la bytes const int cmp = std::memcmp(a.buffer, b.buffer, la); return cmp < 0; } static void BM_Vector_WithString(benchmark::State& state) { const std::size_t N = static_cast<std::size_t>(state.range(0)); // Fixed seed for reproducibility (use random_device() for variability) std::mt19937_64 rng(0xC0FFEEULL); for (auto _ : state) { std::vector<StructWithString> v; v.reserve(N); for (std::size_t i = 0; i < N; ++i) { StructWithString s; s.int_val = static_cast<int>(i); s.double_val = static_cast<double>(i) * 0.5; s.string_val = random_token(rng); v.emplace_back(std::move(s)); } std::sort(v.begin(), v.end(), less_string); benchmark::DoNotOptimize(v.data()); benchmark::ClobberMemory(); } state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) * static_cast<int64_t>(N)); } static void BM_Vector_WithBuffer(benchmark::State& state) { const std::size_t N = static_cast<std::size_t>(state.range(0)); std::mt19937_64 rng(0xBADC0FFEEULL); for (auto _ : state) { std::vector<StructWithBuffer> v; v.reserve(N); for (std::size_t i = 0; i < N; ++i) { StructWithBuffer s{}; s.int_val = static_cast<int>(i); s.double_val = static_cast<double>(i) * 0.5; fill_buffer_from(rng, s.buffer); v.emplace_back(s); } std::sort(v.begin(), v.end(), less_buffer); benchmark::DoNotOptimize(v.data()); benchmark::ClobberMemory(); } state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) * static_cast<int64_t>(N)); } // Register with a useful size sweep BENCHMARK(BM_Vector_WithString)->RangeMultiplier(4)->Range(1 << 8, 1 << 16); BENCHMARK(BM_Vector_WithBuffer)->RangeMultiplier(4)->Range(1 << 8, 1 << 16); BENCHMARK_MAIN();

gjort av ChatGTP BTW, med följande prompt

Resultat på en modern CPU med en modern C++ kompilator? Well, den första med std::string är så klart snabbare! Vilket garanterat INTE hade varit fallet om vi kört med 90-tals CPUer och 90-tals C++-nivå på kompilatorer.

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
Skrivet av Yoshman:

static inline std::string random_token(std::mt19937_64& rng) { std::uniform_int_distribution<int> len_dist(10, 20); int len = len_dist(rng); std::string s; s.resize(static_cast<std::size_t>(len)); for (int i = 0; i < len; ++i) s[static_cast<std::size_t>(i)] = random_ch(rng); return s; }

Fråga: Varför valde du längder mellan 10 och 20 ;), du kan bättre.
Du vet mycket väl att std::string är "small string optimized" och upp till 21 tecken så är kostnaden mindre.
Och i och med att du vet det borde du också förstå varför kod skrivs på olika sätt för att hantera hastighet.

Något som hade varit intressant och se är om kompilatorn genererat annan kod om du väljer gå över vad std::string klarar att lagra utan att allokera minne för det kan vara så att kan kompilatorn kan skriva om koden om den "ser" att en sträng aldrig lagrar mer än vad som får plats i strängens buffer

Permalänk
Datavetare
Skrivet av klk:

Fråga: Varför valde du längder mellan 10 och 20 ;), du kan bättre.
Du vet mycket väl att std::string är "small string optimized" och upp till 21 tecken så är kostnaden mindre.
Och i och med att du vet det borde du också förstå varför kod skrivs på olika sätt för att hantera hastighet.

Något som hade varit intressant och se är om kompilatorn genererat annan kod om du väljer gå över vad std::string klarar att lagra utan att allokera minne för det kan vara så att kan kompilatorn kan skriva om koden om den "ser" att en sträng aldrig lagrar mer än vad som får plats i strängens buffer

Ändrade till slumpmässigt från 1 till 31 tecken, för en av varianterna är inte så "säker" om man har längre strängar. Ändrar inget vad det gäller vilken som är snabbast

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
Skrivet av Yoshman:

Ändrade till slumpmässigt från 1 till 31 tecken, för en av varianterna är inte så "säker" om man har längre strängar. Ändrar inget vad det gäller vilken som är snabbast

<Uppladdad bildlänk>

Tycker du att du gjort ett bra test för vad tror du anledningen skulle vara varför någon att väljer att inte använda std::string utan istället använda en buffer, kan det finnas en orsak till att inte använda std::string

Permalänk
Medlem
Skrivet av klk:

Tycker du att du gjort ett bra test för vad tror du anledningen skulle vara varför någon att väljer att inte använda std::string utan istället använda en buffer, kan det finnas en orsak till att inte använda std::string

För att anknytna till trådens egentliga ämne - minnessäkerhet i C++ - så är det ju minnessäkerhet en stor anledning till att använda std::string istället för en fix buffer.
Använder man std::string för strängar så slipper man manuellt kontrollera att man inte försöker skriva utanför bufferten, och man slipper fundera över vad man skall göra om man får en för lång sträng som skall in där. Två källor till potentiella buggar som helt försvinner. Bara det är en tillräcklig anledning till att välja std::string.

I de fall man har många riktigt korta strängar så kan man ju faktiskt spara lite minne med att använda std::string. Istället för att slösa 32 bytes på varje buffer när merparten av strängarna kanske bara är 3-4 tecken långa så får man mer effektivt utnyttjande av minne.

Vilka anledningar kan man då ha att använda en buffer med fix storlek för att lagra strängar istället för att använda std::string?
Den främsta anledningen är nog okunskap, följt av ohejdad ovana ("så har jag alltid gjort!").

En av de få vettiga anledningarna till att använda en buffert vore väl om det egentligen inte är strängar man skall lagra utan någon form av binärdata som tar exakt 32 bytes. Om den data som skall lagras kan innehålla bytes med värdet 0 så är ju en sträng kanske inte så lämplig att använda.
En annan vettig anledning kan ju vara om man måste matcha något externt definierat format - t.ex. ett filformat eller ett nätverksprotokoll.

Att använda en buffer av fix storlek för att lagra strängar av variabel längd är lite som att använda en skruvmejsel för att slå i en spik.
Det är inte rätt verktyg för jobbet, men det är bättre än inget verktyg alls.

Permalänk
Datavetare
Skrivet av klk:

Tycker du att du gjort ett bra test för vad tror du anledningen skulle vara varför någon att väljer att inte använda std::string utan istället använda en buffer, kan det finnas en orsak till att inte använda std::string

OK, "I did yank your chain a bit". Det relevanta här var att posterna använda längd som primär sorterings nyckel. std::string räknar ut det en gång medan C-style strängar måste räkna ut det varje gång.

Fast nästan själv lite förvånad över att det (ytterst marginellt) tiltar över för std::string även om man bara sorterar på innehåll i strängen. Samtidigt, det är nog kanske ändå förväntat för de som designat std::string har nog applicera fler "tricks in the book" än man först gissar

Detta är om innehållet i strängar används som sorteringsnyckel

Sen till frågan: finns det någon anledning att använda buffertar?
Kan egentligen bara komma på ett enda fall där det är det bättre valet. Har man hårda realtidskrav kombinerat med "safety critical" domän så är det rätt vanligt att dynamisk minnesallokering bara är tillåtet vid systemstart (d.v.s. man måste allokera för "worst-case" direkt och det är en bug att anropa malloc/free, new/delete när programmet börja köra).

I det läget är en C-style buffert vettig då man ändå behöver ta höjd för "worst-case" längd på sträng.

Poängen är ändå: börjar du se att "optimera för cache-line storkel" och "antalet genererade assemblerinstruktioner" inte längre fungerar? Bl.a. så har den dator jag kör på inte 64-bytes cache lines, de är 128 byte (och var inte helt ovanligt med 16 bytes cache-line på en del enklare system jag jobbade med i embeddedvärlden för 10-20 år sedan).

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
Skrivet av Yoshman:

OK, "I did yank your chain a bit". Det relevanta här var att posterna använda längd som primär sorterings nyckel. std::string räknar ut det en gång medan C-style strängar måste räkna ut det varje gång.

Varför väljer du att sortera på sträng, tror du någon skulle välja den lösningen om kravet var att sortera?

Skrivet av Yoshman:

Sen till frågan: finns det någon anledning att använda buffertar?

Massor men just här gjorde jag det maximalt enkelt och det var också en liten baktanke i och med att jag valde längden 32 på buffern
Normalt när denna typ av optimering görs lägger man ju ner en hel del tid (i alla fall gör jag det) och det hanterar mycket mer. Postade en tabellklass tidigare och bara headern där på knappt 2000 rader. Och gjort tre varianter där de är bra på olika saker.

Håller man inte på att skriver operativsystem är det nog sällsynt att skriva så här små klasser och då tänka klockcykler

Skrivet av Yoshman:

Fast nästan själv lite förvånad över att det (ytterst marginellt) tiltar över för std::string även om man bara sorterar på innehåll i strängen. Samtidigt, det är nog kanske ändå förväntat för de som designat std::string har nog applicera fler "tricks in the book" än man först gissar

Ja, är helt övertygad om att det ligger en hel del där kompilatorn vet att det är just std::string, samma som att kompilatorer förstår metoder som memset, memcpy, memcmp och liknande. Gjort en del test med sådan och kompilatorn skriver ofta om koden helt och hållet.
Samtidigt är det svårt att testa prestanda, små tester där man bara kör en viss kod kan få annorlunda resultat om en server är tungt belastad samtidigt som det är just då prestandan behövs.

Intels gamla Core 2 processorer är bra exempel på det. Fantastiskt snabba i tester men så fort belastningen ökade dog de

Permalänk
Medlem

Passar på och posta resultat med kod som liknar det @Yoshman skrev men med en extra variant som också har längden
Generering av text skulle kunna gå att göra något snabbare med buffervarianten, annars så intressant test

länk till koden (som ser förskräcklig ut, lät AI generera): https://godbolt.org/z/E33eP4od4

10-20 i längd Performance comparison: String vs Buffer (with optimizations) ============================================================== Testing with N = 256 elements: String sort (N=256): 55 μs Buffer sort SLOW (N=256): 154.6 μs Buffer sort FAST (N=256): 59.8 μs Speedup from using stored length: 1.66292x Testing with N = 1024 elements: String sort (N=1024): 227.6 μs Buffer sort SLOW (N=1024): 549.2 μs Buffer sort FAST (N=1024): 327.8 μs Speedup from using stored length: 1.88702x Testing with N = 4096 elements: String sort (N=4096): 1850.6 μs Buffer sort SLOW (N=4096): 1928 μs Buffer sort FAST (N=4096): 1274.8 μs Speedup from using stored length: 1.92651x Testing with N = 16384 elements: String sort (N=16384): 7289.8 μs Buffer sort SLOW (N=16384): 6709.4 μs Buffer sort FAST (N=16384): 4087.6 μs Speedup from using stored length: 1.65404x 25-31 i längd Performance comparison: String vs Buffer (with optimizations) ============================================================== Testing with N = 256 elements: String sort (N=256): 67.6 μs Buffer sort SLOW (N=256): 63.2 μs Buffer sort FAST (N=256): 46.6 μs Speedup from using stored length: 2.78333x Testing with N = 1024 elements: String sort (N=1024): 444.2 μs Buffer sort SLOW (N=1024): 384.6 μs Buffer sort FAST (N=1024): 213.2 μs Speedup from using stored length: 2.92431x Testing with N = 4096 elements: String sort (N=4096): 1502.2 μs Buffer sort SLOW (N=4096): 1560.8 μs Buffer sort FAST (N=4096): 1266.8 μs Speedup from using stored length: 1.95623x Testing with N = 16384 elements: String sort (N=16384): 6707.6 μs Buffer sort SLOW (N=16384): 7429 μs Buffer sort FAST (N=16384): 5237.8 μs Speedup from using stored length: 1.56811x 25-31 i längd med avx flagga Performance comparison: String vs Buffer (with optimizations) ============================================================== Testing with N = 256 elements: String sort (N=256): 58.8 μs Buffer sort SLOW (N=256): 61.2 μs Buffer sort FAST (N=256): 44 μs Speedup from using stored length: 2.38333x Testing with N = 1024 elements: String sort (N=1024): 249.6 μs Buffer sort SLOW (N=1024): 274.6 μs Buffer sort FAST (N=1024): 194 μs Speedup from using stored length: 2.33242x Testing with N = 4096 elements: String sort (N=4096): 1065 μs Buffer sort SLOW (N=4096): 1265.4 μs Buffer sort FAST (N=4096): 858.4 μs Speedup from using stored length: 1.96761x Testing with N = 16384 elements: String sort (N=16384): 4711 μs Buffer sort SLOW (N=16384): 5462.8 μs Buffer sort FAST (N=16384): 3607 μs Speedup from using stored length: 1.90442x

Permalänk
Medlem

Det går väl att få lite mer brrrr utan att lägga till ett längd fält. Typ något i stil med:

static auto less_buffer_simd(StructWithBuffer const &a, StructWithBuffer const &b) -> bool { constexpr auto N = 32u; auto const va = _mm256_loadu_si256(reinterpret_cast<__m256i const*>(a.buffer)); auto const vb = _mm256_loadu_si256(reinterpret_cast<__m256i const*>(b.buffer)); auto const vz = _mm256_setzero_si256(); auto const mza = _mm256_cmpeq_epi8_mask(va, vz); auto const mzb = _mm256_cmpeq_epi8_mask(vb, vz); auto const la = mza ? _tzcnt_u32(mza) : N; auto const lb = mzb ? _tzcnt_u32(mzb) : N; if (la != lb) return la < lb; if (la == 0u) return false; auto const m_neq = _mm256_cmpneq_epi8_mask(va, vb); if (la == N && m_neq == 0) return false; auto const m_up = _bzhi_u32(~0u, la); auto const md = m_neq & m_up; if (md == 0u) return false; auto const mlt = _mm256_cmplt_epu8_mask(va, vb) & md; auto const idx = _tzcnt_u32(md); return (mlt >> idx & 1u) != 0u; }

Permalänk
Medlem
Skrivet av Ingetledigtnamn:

(Nu är vi way off topic, men det är vi väl vana vid i den här tråden.)

Ja det har mer blivit stora tråden om allt som har något med programmering att göra men det är kul med lite aktivitet här…

Skrivet av Ingetledigtnamn:

"Allt man behöver" kanske var en liten overstatement. Mina lösningar innehöll inget som inte hade presenterats för de tidigare uppgifterna, men som total APL-n00b var det många frågor som snurrade i huvudet (operatorprecedens? hur sköts iteration?). UI:t på challenge-sidan som oftast bara sade "fel svar" var inte särskilt hjälpsamt. Det krävdes en hel del experimenterande på tryapl för att få till sista lösningen. Min bild av APL baserad på denna sida: funktionell programmering i kubik, klart annorlunda och kul att pröva.

I ärlighetens namn så hade jag faktiskt bara tittat på de två första uppgifterna när jag skrev det där. Nu har jag tittat på alla och står nog fast vid "allt man behöver…" med tillägget "…om man aldrig programmerat tidigare"

Min känsla är att APL är svårare i början om man redan kan programmera eftersom man letar efter motsvarigheter från andra språk. Symbolerna lär man sig snabbt men sättet att tänka skiljer sig rejält. Hur man skriver "bra" kod i APL kan vara precis raka motsatsen till vad som räknas som "bra" kod i andra språk.

Man undviker abstraktioner, namn används sparsamt och kodens struktur är oftast viktigare. Istället för bibliotek med funktioner använder man idioms (korta kodsnuttar) som skrivs inline efter varandra. Fördelen är att hela koden blir mer överskådlig än om logiken är "gömd" i externa moduler och det är lättare med lokala anpassningar.

Det liknar ett linjärt dataflödesorienterat tänk där man fokuserar mer på hur dataflödet transformeras istället för kontrollflöde. På många sätt ligger tankesättet nära DOD. För prestanda är det viktigt hur man organiserar data utifrån hur den används så att den kan bearbetas parallellt och i bulk. Interpretern försöker hela tiden använda minsta möjliga representation av värden (bitbooleans/int8/int16/int32/float64) vilket gör att man kan få effektiv minneslayout och bra cachelokalitet. APL passar egentligen betydligt bättre för dagens datorer med breda SIMD-enheter än de datorer som fanns när APL skapades.

Det korta svaret på "operatorprecedens? hur sköts iteration?" är, operatorprecedens finns inte och man undviker att iterera.

Det något längre svaret hamnar i en spoiler tag

Koden körs från höger till vänster och alla funktioner har kort vänster scope (det som står direkt till vänster om funktionen) och långt höger scope (allt på höger sida). Om man hänger med i hur 3 × 2 2 + 1 resulterar i en array med två element 9 9 så vet man i princip allt man behöver om precedens när det gäller funktioner. Vill man ändra ordningen kan man använda parenteser, men ofta är det bättre att använda operatorn ⍨ (Haskells flip). (1+2+3+…)÷2 skrivs 2÷⍨1+2+3+… ,vilket gör att man slipper hoppa fram och tillbaka när man läser.

Angående iterering så kan man oftast undvika det och arbeta med funktioner (t.ex reduce /⌿ och scan \⍀) på hela arrayen eller med ett urval genom att använda en mask (1 0 1 0/'abcd') ≡ 'ac' eller indexering.

Men det finns flera sätt att iterera:
f¨ - (each, map i andra spåk). APL har något som kallas för scalar pervasion vilket innebär att skalära funktioner tillämpas på enskilda element i en array oavsett struktur (t.ex nästlade arrays). (1 (2 3)) + 1 resulterar i (2 (3 4)), (1 (2 3)) × 10 100 -> (10 (200 300)). Därför behövs bara ¨ för icke-skalära funktioner och egendefinierad funktioner.

f⍣N (power operator) - upprepar funktionen f N gånger. 2(×⍣3)10 eller ×∘2⍣3⊢10 är samma sak som 2×2×2×10. Eller för att ta ett mer avancerat exempel. Fibonacci genom upprepad matris multiplikation {⊃(+.×⍣⍵)⍨2 2⍴0 1 1 1} (⊃ - first, f.g - outer product, +.× - matrix mul).

f⍣g Y - Med en funktion till höger upprepar funktionen f tills f Y g Y är sant. f⍣≡ kan alltså användas för att beräkna en fixpoint - när f inte längre ger ett nytt värde. T.ex kan man beräkna nåbarhet (transitive closure) i en adjacency matrix graf genom ∨.∧⍨⍣≡ (outer product med or och and som upprepas så länge matrisen ändras mellan varje steg). n³×log n är inte direkt det effektivaste sättet men koden är betydligt kortare än vad bara ett "bra" namn på funktionen skulle vara i ett annat språk.

f⍣¯N - Upprepar funktionen f negativt N gånger… man tar helt enkelt inversen av funktionen f N gånger. ⍸ - where - returnerar index satta till 1 en boolean array. ⍸0 0 1 0 1 returnerar 3 5. Inversen ⍸⍣¯1⊢3 5 returnerar en boolean array med element 3 och 5 satt till 1.

Det går även att skriva rekursiva funktioner med TCO. Man refererar till funktionen själv med ∇.
Naiv rekursiv fibonacci: {⍵≤1:⍵ ⋄ (∇⍵-1)+(∇⍵-2)} eller lite kortare {⍵≤1:⍵ ⋄ +/∇¨⍵-⍳2}. (⋄ fungerar som en newline, ⍵≤1:⍵ körs först och sen +/∇¨⍵-⍳2).
Med vänster argument ⍺ som ackumulator {⍺←0 1 ⋄ ⍵=0:⊃⍺ ⋄ (1↓⍺,+/⍺)∇⍵-1}.

Några bra APL resurser Learning APL (modern APL med jämförelser i python), APL Course, Mastering Dyalog APL, APL Quest (tidigare APL Problem Solving Competition - med videogenomgångar för lösningar), Learn APL with Neural Networks (video serie).

Dold text
Permalänk
Medlem
Skrivet av klk:

länk till koden (som ser förskräcklig ut, lät AI generera): https://godbolt.org/z/E33eP4od4

För någon som, enligt egen utsago, skriver tusentals rader kod dagligen, så skriver du väldigt lite kod, och är väldigt snabb att ta till LLM.

Permalänk
Datavetare
Skrivet av klk:

Varför väljer du att sortera på sträng, tror du någon skulle välja den lösningen om kravet var att sortera?

Det var bara ett, av oändligt många, sätt man kan tänkas vilja sortera/klassificera/... sina poster. En poäng där var att just information om längden hos en sträng är rätt vanligt att man vill veta, men det saknades ju i din buffer-lösning och kostade extra att räkna ut varje gång.

Nu fixade du den grejen. Och visst kan du göra den fina optimering som @jclr gjorde, men passar inte något sådant bättre ihop med en CPU/ISA-specifik optimering för std::string i delen som används för small-string optimization (efter som för att göra sådant måste man vara säker att när man läser förbi '\0' så får man bara läsa sådant som är garanterat "safe", vilket det är om man sagt åt kompilatorn "det finns alltid 32 bytes" här, men det är inte safe för en char* då '\0' skulle kunna ligga precis på en page-gräns och virtuella adressen efter är inte mappad. Nu går även det att reda ut om man vet storlek på page:es, men det börjar kanske så man fokuserar på fel sak).

Skrivet av klk:

Passar på och posta resultat med kod som liknar det @Yoshman skrev men med en extra variant som också har längden
Generering av text skulle kunna gå att göra något snabbare med buffervarianten, annars så intressant test

länk till koden (som ser förskräcklig ut, lät AI generera): https://godbolt.org/z/E33eP4od4

10-20 i längd Performance comparison: String vs Buffer (with optimizations) ============================================================== Testing with N = 256 elements: String sort (N=256): 55 μs Buffer sort SLOW (N=256): 154.6 μs Buffer sort FAST (N=256): 59.8 μs Speedup from using stored length: 1.66292x Testing with N = 1024 elements: String sort (N=1024): 227.6 μs Buffer sort SLOW (N=1024): 549.2 μs Buffer sort FAST (N=1024): 327.8 μs Speedup from using stored length: 1.88702x Testing with N = 4096 elements: String sort (N=4096): 1850.6 μs Buffer sort SLOW (N=4096): 1928 μs Buffer sort FAST (N=4096): 1274.8 μs Speedup from using stored length: 1.92651x Testing with N = 16384 elements: String sort (N=16384): 7289.8 μs Buffer sort SLOW (N=16384): 6709.4 μs Buffer sort FAST (N=16384): 4087.6 μs Speedup from using stored length: 1.65404x 25-31 i längd Performance comparison: String vs Buffer (with optimizations) ============================================================== Testing with N = 256 elements: String sort (N=256): 67.6 μs Buffer sort SLOW (N=256): 63.2 μs Buffer sort FAST (N=256): 46.6 μs Speedup from using stored length: 2.78333x Testing with N = 1024 elements: String sort (N=1024): 444.2 μs Buffer sort SLOW (N=1024): 384.6 μs Buffer sort FAST (N=1024): 213.2 μs Speedup from using stored length: 2.92431x Testing with N = 4096 elements: String sort (N=4096): 1502.2 μs Buffer sort SLOW (N=4096): 1560.8 μs Buffer sort FAST (N=4096): 1266.8 μs Speedup from using stored length: 1.95623x Testing with N = 16384 elements: String sort (N=16384): 6707.6 μs Buffer sort SLOW (N=16384): 7429 μs Buffer sort FAST (N=16384): 5237.8 μs Speedup from using stored length: 1.56811x 25-31 i längd med avx flagga Performance comparison: String vs Buffer (with optimizations) ============================================================== Testing with N = 256 elements: String sort (N=256): 58.8 μs Buffer sort SLOW (N=256): 61.2 μs Buffer sort FAST (N=256): 44 μs Speedup from using stored length: 2.38333x Testing with N = 1024 elements: String sort (N=1024): 249.6 μs Buffer sort SLOW (N=1024): 274.6 μs Buffer sort FAST (N=1024): 194 μs Speedup from using stored length: 2.33242x Testing with N = 4096 elements: String sort (N=4096): 1065 μs Buffer sort SLOW (N=4096): 1265.4 μs Buffer sort FAST (N=4096): 858.4 μs Speedup from using stored length: 1.96761x Testing with N = 16384 elements: String sort (N=16384): 4711 μs Buffer sort SLOW (N=16384): 5462.8 μs Buffer sort FAST (N=16384): 3607 μs Speedup from using stored length: 1.90442x

Poängen är främst: varför lägga så mycket tid på en sådan sak? Det är redan ett löst problem och "small string optimization" finns av en väldigt bra anledning.

Du verkar gilla att uppfinna egna lösningar, som inte på något sätt verkar bättre än existerande lösning. Varför skrev du t.ex. om det google-benchmark redan gjort (och där det finns en hel del minor att kliva på, som vid det här laget lär vara röjda i just google-benchmark)?

Just std::string har fått en hel del optimeringar av "jättarna" (Meta, Google, m.fl.) just då de har kodbaser där prestanda för just strängar är viktigt. Man kan absolut ha extremt specifika behov som ändå gör att det krävs speciallösningar, men har verkligen det du jobbar det och om så: vore spännande att höra varför

BTW: om jag kör på Windows/i7-12700T/MSVC++ så kan jag rätt mycket reproducera ditt resultat. Kör jag på MacOS/M4/Clang++ så är båda varianterna rätt mycket exakt lika snabba (std::string är ytterst marginellt snabbare). Kör jag på Ubuntu/U5-225H/g++ hamnar man någonstans mitt emellan. Den här typen av "optimeringar" är rätt bräckliga, just det här fallet råkade det ändå bli så att din speciallösning i alla fall inte blev sämre. Men den är svårare att begripa, "ingen" kommer vara bekant med den och skulle antagligen inte behövas speciellt mycket fantasi för att hitta fall där den är långsammare än std::string.

Just standard-biblioteken är något man ska extremt goda skäl innan man byte mot speciallösnignar. Om man ofta har problem med standard-biblioteken jobbar man nog i en miljö som är illa anpassad till sin domän.

Och stötte också på en av orsakerna varför C++ aldrig slagit ut C inom många system-områden: försökte köra MacOS varianten med g++-15. Gick inte att länka då clang++ och varianten av g++-15 som finns i Homebrew producera icke-kompatibla bibliotek (kör med google-benchmark från Homebrew som är byggd med clang++)... Att inte standardisera ABI är en "WTF Bjarne???".

Du pratar också mycket om dina "tabeller" och hur de kan användas som en "databas i RAM".

Hur är det annorlunda och bättre än denna standardlösning (som kanske inte blir fullt lika "SQL-like" med C++ standardfunktioner, men går ändå att göra rätt 1:1 logiskt sett)?

public readonly record struct User(string Name, int Age) {} public readonly record struct Event(int UserId, DateTime Start, DateTime End) {} public readonly record struct UserEvent(string Name, int Age, DateTime Start, DateTime End) {} class Program { static void Main() { var users = new Dictionary<int, User> { [1] = new User("Alice", 30), [2] = new User("Bob", 28), }; var now = DateTime.Today; var events = new Dictionary<int, Event> { [100] = new Event(1, now.AddHours(9), now.AddHours(10)), // Alice [101] = new Event(2, now.AddHours(8), now.AddHours( 9)), // Bob [102] = new Event(1, now.AddHours(13), now.AddHours(14)), // Alice [103] = new Event(2, now.AddHours(11), now.AddHours(12)), // Bob [104] = new Event(1, now.AddHours(15), now.AddHours(16)), // Alice }; var joined = events.Values .Join(users.Values, e => e.UserId, u => u.Id, (e, u) => new { e, u }) .OrderBy(x => x.e.Start) .Select(x => new UserEvent(x.u.Name, x.u.Age, x.e.Start, x.e.End)) .ToList(); foreach (var ue in joined) Console.WriteLine($"{ue.Name} ({ue.Age}) {ue.Start:t}–{ue.End:t}"); } }

Dold text
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
Skrivet av klk:

Testing with N = 256 elements: String sort (N=256): 55 μs Buffer sort SLOW (N=256): 154.6 μs Buffer sort FAST (N=256): 59.8 μs Speedup from using stored length: 1.66292x … … Testing with N = 256 elements: String sort (N=256): 67.6 μs Buffer sort SLOW (N=256): 63.2 μs Buffer sort FAST (N=256): 46.6 μs Speedup from using stored length: 2.78333x

Det kan vara jag som är trög men ~1.7× i det ena fallet och ~2.8× i det andra ser inte ut att hänga ihop med timing resultaten?

Skrivet av Yoshman:

Du verkar gilla att uppfinna egna lösningar, som inte på något sätt verkar bättre än existerande lösning. Varför skrev du t.ex. om det google-benchmark redan gjort (och där det finns en hel del minor att kliva på, som vid det här laget lär vara röjda i just google-benchmark)?

Ja helt egenskriven benchmark kod ska man nog inte lämna över till en AI att skriva…

Permalänk
Medlem
Skrivet av jclr:

Det kan vara jag som är trög men ~1.7× i det ena fallet och ~2.8× i det andra ser inte ut att hänga ihop med timing resultaten?

Kolla koden för de siffrorna är från ett sista test, alltså inte från själva mätresultaten

Att LLM gjorde så tror jag beror på att den ville generera kod som specifikt mätte skillnader och inte tog med generering av data

Permalänk
Medlem
Skrivet av Xeonist:

För någon som, enligt egen utsago, skriver tusentals rader kod dagligen, så skriver du väldigt lite kod, och är väldigt snabb att ta till LLM.

Absolut, LLM (som jag också använder) kan producera enorma mängder men skit in blir skit ut.
Om bara man vill producera massor av kod är det lätt idag. Vill du däremot producera kvalitet så kan du inte låta LLM generera all kod (långt ifrån). LLM är ett verktyg och precis som många andra verktyg tar det tid att lära sig hur verktyget skall användas på bästa sätt.

LLM är superbra på att producera enorma mängder skit vilket är lätt att glömma

Permalänk
Skrivet av Yoshman:

Och stötte också på en av orsakerna varför C++ aldrig slagit ut C inom många system-områden: försökte köra MacOS varianten med g++-15. Gick inte att länka då clang++ och varianten av g++-15 som finns i Homebrew producera icke-kompatibla bibliotek (kör med google-benchmark från Homebrew som är byggd med clang++)... Att inte standardisera ABI är en "WTF Bjarne???".

Det loppet var förlorat redan innan det startade. Baseras språket på C, som inte specar hur lång en int skall vara, är det kört för ett standardiserat ABI. Bortsett från det, så tycker jag att språket inte skall försöka styra hur det implementeras. Olika plattformar kommer ha helt olika förutsättningar. Jag skulle gissa att det är flera av oss i tråden som en gång i tiden kört på maskiner där sizeof(int) inte var en power-of-two. Att speca en fix längd (säg 32 bitar) på typer kommer lägga extra börda på kompilatorimplementatörer när CPUns naturliga operationsstorlek inte är 32 bitar.

Det funkar att göra om man samtidigt specar den virtuella maskinen som skall köra koden (Java) eller fokuserar på sin egen plattform (Swift). Men annars tror jag att det kan hämma spridningen av språket. Det mesta är numera 32/64-bitar, men det finns fortfarande embedded controllers där 32-bits int skulle vara kostsamt.

Permalänk
Medlem
Skrivet av Yoshman:

Det var bara ett, av oändligt många, sätt man kan tänkas vilja sortera/klassificera/... sina poster. En poäng där var att just information om längden hos en sträng är rätt vanligt att man vill veta, men det saknades ju i din buffer-lösning och kostade extra att räkna ut varje gång.

Precis, det saknades längd och det var medvetet. Samma som det var medvetet att buffern hade 32 byte och tänkte förklara varför.

strukten hade en speciell "design" där en van utvecklare snabbt ser var den kan vara användbar men också se var den är inte spelar någon större roll för hastigheten.

Nära nog alla som testat prestanda och då även kollar benchmarks vet ju om hur std::string fungerar. Klassen är så vanlig och spelar därför stor roll i nästan all kod. Om inte annat så finns det anledningar till varför det också finns en std::string_view .

Flera i tråden har också räknat upp en hel del fina ord om hur processorer gör bla bla bla och ibland undrar man om de tror att processorer kan trolla Processorer kan inte trolla. Så svårt är det inte

Exemplet var alltså för att visa att det snabbt går att se på DATA vad som går att göra och varför det är snabbt för vissa operationer men kanske inte passar för annat. Och detta är inte speciellt svårt.

Skrivet av Yoshman:

Nu fixade du den grejen. Och visst kan du göra den fina optimering som @jclr gjorde

Hade varit intressant om den optimering @jclr gjorde faktiskt var snabbare under förutsättning att det är samma funktionalitet. Har själv gjort en del optimeringar med avx men det är så hiskeligt mycket att hålla reda på och kompilatorer gör ett riktigt bra jobb med att fixa sådant här. Det är svårt att slå kompilatorer idag, något som inte alls var så svårt förr i tiden.

Skrivet av Yoshman:

Poängen är främst: varför lägga så mycket tid på en sådan sak? Det är redan ett löst problem och "small string optimization" finns av en väldigt bra anledning.

Det gör man inte, koden i detta exempel är inte realistisk sett till vanliga problem. Möjligen i drivare för saker som måste vara så optimerade som bara går, operativsystem eller liknande. Men för vanlig kod är detta alldeles för petigt.

Skrivet av Yoshman:

Du verkar gilla att uppfinna egna lösningar, som inte på något sätt verkar bättre än existerande lösning.

Om jag kan göra något bättre än befintliga lösningar så ja, givetvis måste det vara en poäng.

Skrivet av Yoshman:

Varför skrev du t.ex. om det google-benchmark redan gjort (och där det finns en hel del minor att kliva på, som vid det här laget lär vara röjda i just google-benchmark)?

Ville göra koden enkel att dela och lätt att testa varianter på nätet, känner inte till hur det görs med google-benchmark och den behöver extra kod, kod som inte följer med i C++. LLM är fantastiska på att fixa till testkod men håller med om att koden som genererades var rätt knölig, förtränade inte.

Skrivet av Yoshman:

men har verkligen det du jobbar det och om så: vore spännande att höra varför

Jobbar med mycket stora mängder data men också säkerhet. HPC cluster som räknar och det blir en del.

Skrivet av Yoshman:

BTW: om jag kör på Windows/i7-12700T/MSVC++ så kan jag rätt mycket reproducera ditt resultat. Kör jag på MacOS/M4/Clang++ så är båda varianterna rätt mycket exakt lika snabba (std::string är ytterst marginellt snabbare). Kör jag på Ubuntu/U5-225H/g++ hamnar man någonstans mitt emellan. Den här typen av "optimeringar" är rätt bräckliga, just det här fallet råkade det ändå bli så att din speciallösning i alla fall inte blev sämre. Men den är svårare att begripa, "ingen" kommer vara bekant med den och skulle antagligen inte behövas speciellt mycket fantasi för att hitta fall där den är långsammare än std::string.

Hur ser cachen ut på Mac? Kan tänka mig att om data får plats i snabbare cache så spelar det mindre roll om det är std::string eller inte så länge all data får plats där. Först när cachen börjar fyllas, antingen om det är mycket data eller det är flera andra trådar som tävlar om utrymme tror jag skillnaderna blir större. Prefetchers kommer då fungera bättre på buffer varianten jämfört med den med std::string.
Vad jag känner till med så är Clang duktig på att optimera kod för apples processorer. Microsofts kompilator är vad jag vet den sämsta, de ligger efter om de inte ryckt upp sig det senaste.

Permalänk
Datavetare
Skrivet av Ingetledigtnamn:

Det loppet var förlorat redan innan det startade. Baseras språket på C, som inte specar hur lång en int skall vara, är det kört för ett standardiserat ABI. Bortsett från det, så tycker jag att språket inte skall försöka styra hur det implementeras. Olika plattformar kommer ha helt olika förutsättningar. Jag skulle gissa att det är flera av oss i tråden som en gång i tiden kört på maskiner där sizeof(int) inte var en power-of-two. Att speca en fix längd (säg 32 bitar) på typer kommer lägga extra börda på kompilatorimplementatörer när CPUns naturliga operationsstorlek inte är 32 bitar.

Det funkar att göra om man samtidigt specar den virtuella maskinen som skall köra koden (Java) eller fokuserar på sin egen plattform (Swift). Men annars tror jag att det kan hämma spridningen av språket. Det mesta är numera 32/64-bitar, men det finns fortfarande embedded controllers där 32-bits int skulle vara kostsamt.

Fast C++ har tagit det ännu ett steg längre.

Helt OK att sizeof(int) är olika storlek med avseende på olika CPU-arch/OS-kombos. Det fanns absolut en logik bakom varför man gjorde det valet i C:s tidiga dagar.

Du nämner Swift och det finns ju till flera CPU-arch/OS-kombon och där har man likt C/C++ valt att sizeof(int) är 4 bytes på 32-bit och 8 bytes på 64-bit. Däremot har C# och Java båda valt att sizeof(int) alltid är 4 bytes.

Omvänt har C/C++ har infört int32_t, int64_t från ISO C99 och framåt.

Men i fallet C++ har man ju haft problemet att ett bibliotek kan vara inkompatibelt med din applikation trots samma CPU-arch/OS-kombo, men olika kompilatorer. C har inte det problemet.

Sen kan man absolut peka på att flera lite mindre språk kanske bara har en relevant tool-chain och då blir det såklart inget problem på en given CPU-arch/OS-kombo.

Nog rätt mycket programvara som aldrig fixat det mer ovanliga varianterna hos C. Har jobbat lite med DSP:er där minsta adresserbara enhet, det C/C++ definierar till en char, var 16-bits. Var rätt få saker som fungerade "out-of-the-box" till dessa...

Idag kan man rätt mycket klassa big-endian som "historia". Little-endian "vann" och även POWER som tidigare oftast var big-endian är idag little-endian. Undrar om det längre finns DPS/MCUer där minsta adresserbar enhet är något annat än 8-bitar?
Flera val som egentligen inte spelade någon relevant roll har tack och lov konvergerat, trots en stark "network-background" är jag ändå helt OK med little-endian som "vinnare" (network-byte-order och big-endian var ju synonymer).

Visa signatur

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