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

Permalänk
Medlem
Skrivet av Yoshman:

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)?

"Problemet" med den lösningen (problem är självklart subjektivt) är att du har flera allokeringar och lösningen är användardomän specifik. Det är ok när det är små exempel eller kanske mindre kod av andra anledningar. När koden växer blir sådant här svårare och svårare att hantera eftersom användardomänen måste läras av andra utvecklare, det ingår inte i deras kunskaper.

Att lösa buggar är också något som kan vara en stor utmaning för användardomänen då utvecklaren ofta måste fråga någon annan som vet hur det skall fungera.

Går det att få bort användardomänen eller göra den så liten som möjligt (tunt lager) och sedan få resten av koden att anpassa sig automatiskt vinner man massor.

Till exempel så är ditt exempel likt SQL som du nämner. Kod med massa SQL i sig är inte rolig för det blir trassligt och tungt att göra rättningar då det måste ut nya versioner. Skulle jag göra en lösning för det där så hade jag försökt lyfta bort SQL logiken och kanske läst in den informationen som metadata.
Det hade självklart varit enormt överjobbat för så här litet exempel men för normalt större system är den kostnaden ganska snabbt intjänad när system växer eller behöver ändringar

Permalänk
Datavetare
Skrivet av klk:

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.

sysctl hw|grep cache hw.perflevel0.l1icachesize: 196608 hw.perflevel0.l1dcachesize: 131072 hw.perflevel0.l2cachesize: 16777216 hw.perflevel1.l1icachesize: 131072 hw.perflevel1.l1dcachesize: 65536 hw.perflevel1.l2cachesize: 4194304 hw.cacheconfig: 16 1 4 0 0 0 0 0 0 0 hw.cachesize: 3363192832 65536 4194304 0 0 0 0 0 0 0 hw.cachelinesize: 128 hw.l1icachesize: 131072 hw.l1dcachesize: 65536 hw.l2cachesize: 4194304

Så 192 kB L1I$, 128 kB L1D$, 16 MB L2$. Med 128 byte cacheline. (Sen använder MacOS 16 kB page-size, Windows kör med 4 kB och Linux stöder numera 4, 16 och 64 kB page för ARM64).

L2$ är för 4st kärnor, sett spekulation om att till skillnad från t.ex. Intel och AMD där L2$ är lokal till en kärna (P-kärnor) eller 4 kärnor (Intel E-kärnor) så kan M-serien även använda andra kärnors L2$ (med högre latens) i lägen där en/få kärnor är aktiva.

Undrar själv om det egentligen var en clang vs gcc vs MSVC++ effekt. Nog sannolikare att det kommer av skillnader från hur std::string är implementerad. Clang kommer med libc++ vilket är standard på MacOS, GCC kommer med libstd++ och Microsoft har också sitt eget standardbibliotek.

Men naturligtvis kan kompilatorn påverka. Sett folk få goda resultat (i.e. bättre prestanda än med MSVC++) med Clang (ihop med Microsofts C++ standardbibliotek) på Windows. Visual Studio stödjer sedan en tid tillbaka officiellt även Clang som C++ kompilator.

Snabb sökning säger att Clang-stöd har funnits sedan VS2015, sedan VS2019 verkar det anses "stable".

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 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.

Knappast. Det finns ett standardiserat ABI för C på många plattformar. Man har helt enkelt bestämt att "På den här plattformen är int 32 bitar, long 32 bitar, pekare 64 bitar, osv". Och även bestämt hur funktionsnamn, variabelnamn, m.m. skall se ut i objektfiler och bibliotek, och bestämt hur argument och resultat skickas till/från funktioner. Med mera.
Med resultat att man kan blanda filer kompilerade av olika kompilatorer.

Mycket av det där hänger med till C++. Men inte allt. Hur namn på metoder och liknande manglas innan de hamnar i objektfiler är inte standardiserat, så alla gör på lite olika sätt, vilket gör att i princip allting i ett program måste kompileras av exakt samma kompilator.

Permalänk
Datavetare
Skrivet av klk:

"Problemet" med den lösningen (problem är självklart subjektivt) är att du har flera allokeringar och lösningen är användardomän specifik. Det är ok när det är små exempel eller kanske mindre kod av andra anledningar. När koden växer blir sådant här svårare och svårare att hantera eftersom användardomänen måste läras av andra utvecklare, det ingår inte i deras kunskaper.

Att lösa buggar är också något som kan vara en stor utmaning för användardomänen då utvecklaren ofta måste fråga någon annan som vet hur det skall fungera.

Går det att få bort användardomänen eller göra den så liten som möjligt (tunt lager) och sedan få resten av koden att anpassa sig automatiskt vinner man massor.

Till exempel så är ditt exempel likt SQL som du nämner. Kod med massa SQL i sig är inte rolig för det blir trassligt och tungt att göra rättningar då det måste ut nya versioner. Skulle jag göra en lösning för det där så hade jag försökt lyfta bort SQL logiken och kanske läst in den informationen som metadata.
Det hade självklart varit enormt överjobbat för så här litet exempel men för normalt större system är den kostnaden ganska snabbt intjänad när system växer eller behöver ändringar

Du blandar väl ändå in mindre domänspecifika prylar genom att hålla dig till standardbiblioteket? Det kommer ju alla kunna som kan C++. Och en stor fördel med standardbiblioteket är att det väldigt sällan har kvar buggar som triggar på någorlunda normal användning.

Var skulle du kunna kapa allokeringar i detta fall (som likt C# versionen håller tabell-posterna by-value)*

#include <algorithm> #include <chrono> #include <ctime> #include <iomanip> #include <iostream> #include <map> #include <string> #include <vector> struct User { std::string Name; int Age; }; using Clock = std::chrono::system_clock; using Seconds = std::chrono::seconds; struct Event { int UserId; Clock::time_point Start; Clock::time_point End; }; struct UserEvent { std::string Name; int Age; Clock::time_point Start; Clock::time_point End; }; // Small helper to print HH:MM from a time_point (local time) static void print_time_hm(Clock::time_point tp) { std::time_t tt = Clock::to_time_t(tp); std::tm local{}; #if defined(_WIN32) localtime_s(&local, &tt); #else local = *std::localtime(&tt); #endif std::cout << std::put_time(&local, "%H:%M"); } int main() { std::map<int, User> users{ {1, {"Alice", 30}}, {2, {"Bob", 28}}, }; using namespace std::chrono; const auto today_midnight = floor<days>(Clock::now()); auto h = [&](int hours_from_midnight) { return today_midnight + hours{hours_from_midnight}; }; std::map<int, Event> events{ {100, {1, h(9), h(10)}}, // Alice {101, {2, h(8), h(9)}}, // Bob {102, {1, h(13), h(14)}}, // Alice {103, {2, h(11), h(12)}}, // Bob {104, {1, h(15), h(16)}}, // Alice }; std::vector<UserEvent> joined; joined.reserve(events.size()); for (const auto& [eventId, e] : events) { if (auto it = users.find(e.UserId); it != users.end()) { const auto& u = it->second; joined.emplace_back(UserEvent{u.Name, u.Age, e.Start, e.End}); } } std::sort(joined.begin(), joined.end(), [](const UserEvent& a, const UserEvent& b) { return a.Start < b.Start; }); for (const auto& ue : joined) { std::cout << ue.Name << " (" << ue.Age << ") "; print_time_hm(ue.Start); std::cout << "–"; print_time_hm(ue.End); std::cout << "\n"; } return 0; }

Kräver C++20
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:

Du blandar väl ändå in mindre domänspecifika prylar genom att hålla dig till standardbiblioteket? Det kommer ju alla kunna som kan C++. Och en stor fördel med standardbiblioteket är att det väldigt sällan har kvar buggar som triggar på någorlunda normal användning.

Var skulle du kunna kapa allokeringar i detta fall (som likt C# versionen håller tabell-posterna by-value)*

Vinsterna ligger inte i små exempel.

Om jag frågar så här istället.

Du ha ett "UserEvent " objekt, men säg istället att du har någon form av användarobjekt och det kommer nya krav på hur användare skall hanteras. De skall vara anställda och måste kopplas till organisation/företag, de skall också ha adresser, kan vara flera olika. De skall också kopplas till intressen/egenskaper.

Hur gör du för att få sådan kod smidig och jobba med och vara beredd på att det snabbt kan ändra sig?

Postar den här koden från mitt lilla sökprogram, hur sökprogrammet lagrar information. Hade jag hårdkodat det här i olika structer tror jag det minst tagit kanske fem gånger så lång tid och inte alls lika flexibelt

/** --------------------------------------------------------------------------- @TAG #cache * @brief Prepares a cache table for the specified identifier. * * This method initializes and prepares a table for caching data associated with the given `stringId`. * If the `stringId` matches specific identifiers like "file", "file-count", or "file-linelist" etc * it creates tables with predefined columns tailored for their respective purposes: * * The table is then added to the internal application cache. * * @param stringId A string view representing the identifier for the cache table. * * @details * - The method first checks if a cache table with the given `stringId` already exists using `CACHE_Get`. * - If the table does not exist, it creates a new `table` object with predefined columns. * - The table is wrapped in a `std::unique_ptr` and added to the cache using `CACHE_Add`. * * @note This method assumes that the `CACHE_Add` function handles the ownership of the table. */ void CDocument::CACHE_Prepare(const std::string_view& stringId, std::unique_ptr<gd::table::dto::table>* ptable) // @TAG #data.cache { LOG_VERBOSE_RAW("== Prepare table:" << stringId << "CDocument::CACHE_Prepare"); using namespace gd::table::dto; constexpr unsigned uTableStyle = ( table::eTableFlagNull32 | table::eTableFlagRowStatus ); if( ptable == nullptr ) { auto ptableFind = CACHE_Get(stringId, false); if( ptableFind != nullptr ) return; // table already exists, exit } std::unique_ptr<gd::table::dto::table> ptable_; // ## prepare file list // columns: "path, size, date, extension if( stringId == "file" ) // file cache, used to store file information { auto p_ = CACHE_Get(stringId, false); if( p_ == nullptr ) { // file table: key | path | size | date | extension ptable_ = std::make_unique<table>(table(uTableStyle, { {"uint64", 0, "key"}, {"rstring", 0, "folder"}, {"rstring", 0, "filename"}, {"uint64", 0, "size"}, {"double", 0, "date"}, {"string", 20, "extension"} }, gd::table::tag_prepare{})); ptable_->property_set("id", stringId); // set id for table, used to identify table in cache } } else if( stringId == "file-dir" ) // file cache, used to store file information { auto p_ = CACHE_Get(stringId, false); if( p_ == nullptr ) { // file table: key | path | size | date | extension ptable_ = std::make_unique<table>(table(uTableStyle, { {"uint64", 0, "key"}, {"rstring", 0, "path"}, {"uint64", 0, "size"}, {"double", 0, "date"}, {"string", 20, "extension"} }, gd::table::tag_prepare{})); ptable_->property_set("id", stringId); // set id for table, used to identify table in cache } } else if( stringId == "file-count" ) // row counter table { auto p_ = CACHE_Get(stringId, false); if( p_ == nullptr ) { // file-count table: key | file-key | filename // count | code | characters | comment | string ptable_ = std::make_unique<table>(table(uTableStyle, { {"uint64", 0, "key"}, {"uint64", 0, "file-key"}, {"rstring", 0, "filename"}, {"uint64", 0, "count"}, {"uint64", 0, "code"}, {"uint64", 0, "characters"}, {"uint64", 0, "comment"}, {"uint64", 0, "string"} }, gd::table::tag_prepare{}) ); ptable_->property_set("id", stringId); // set id for table, used to identify table in cache } } else if( stringId == "file-linelist" ) // lists line where pattern was found { auto p_ = CACHE_Get(stringId, false); if( p_ == nullptr ) { // file-linelist table: key | file-key | filename // line | row | column | pattern, segment // line = the row in text where pattern was found ptable_ = std::make_unique<table>(table(uTableStyle, { {"uint64", 0, "key"}, {"uint64", 0, "file-key"}, {"rstring", 0, "filename"}, {"rstring", 0, "line"}, {"uint64", 0, "row"}, {"uint64", 0, "column"}, {"string", 32, "pattern"}, {"string", 10, "segment"} }, gd::table::tag_prepare{}) ); ptable_->property_set("id", stringId); // set id for table, used to identify table in cache } } else if( stringId == "file-snippet" ) { ptable_ = std::make_unique<table>(table(uTableStyle, { {"uint64", 0, "key"}, {"uint64", 0, "file-key"}, {"rstring", 0, "filename"}, {"string", 10, "format"}, {"uint64", 0, "row"}, {"rstring", 0, "snippet"} }, gd::table::tag_prepare{}) ); ptable_->property_set("id", stringId); // set id for table, used to identify table in cache } else if( stringId == "keyvalue" ) { auto ptableKeys = std::make_unique<gd::table::arguments::table>( gd::table::tag_full_meta{} ); ptableKeys->column_prepare(); ptableKeys->column_add("uint64", 0, "key"); // add key column ptableKeys->column_add("uint64", 0, "file-key"); // foreign key to file table ptableKeys->column_add("uint64", 0, "file-linelist-key"); // foreign key to file-linelist table ptableKeys->column_add("rstring", 0, "filename"); // name of file ptableKeys->column_add("uint64", 0, "row"); // row number in file ptableKeys->prepare(); // prepare table ptableKeys->property_set("id", stringId); // set id for table, used to identify table in cache CACHE_Add( std::move(ptableKeys) ); // add table to cache return; // exit, table is already added to cache } else if( stringId == "history" ) { // file table: index | date | command | line ptable_ = std::make_unique<table>(table(uTableStyle, { {"uint64", 0, "key"}, {"int32", 0, "index"}, {"string", 30, "date"}, {"string", 20, "name"}, {"rstring", 0, "line"}, {"rstring", 0, "alias"} }, gd::table::tag_prepare{})); ptable_->property_set("id", stringId); // set id for table, used to identify table in cache } else { assert(false); } // unknown cache table if( ptable != nullptr ) { *ptable = std::move(ptable_); // move table to caller } else { CACHE_Add(std::move(*ptable_)); // add it to internal application cache } }

Dold text
Permalänk
Skrivet av Yoshman:

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.

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.

Nja, att C inte har det problemet är kanske att ta i. GCC kan lägga ut anrop till hjälpfunktioner i libgcc så det är inte säkert att du kan länka GCC-genererad kod med clang rakt av om du valt att använda compiler-rt. På plattformar där clang använder GCCs bibliotek är detta inget som helst problem, men det beror på val som clang-implementatörerna gjort, inte på att C int ekan råka ut för liknande problem. Hur ser det ut på Windows? Där borde anropen till hjälpfunktionerna också kunna ställa till det.

Den tajta kopplingen mellan kompilator och dess bibliotek är olycklig, men om man krävde att all kod som genererats av kompilator A skulle kunna länkas mot kompilator Bs bibliotek skulle inte språkdefinitionen behöva speca allt då? Objektlayout, explicit med hur alla detaljer som virtuella baser, vtables, RTTI ska hanteras. Exception-mekanismen, inga Structured Exceptions på Windows Exakt hur alla templates skall vara definierade, inte bara layout utan även alla medlemsfunktioner. Annars riskerar du att bryta mot One Definition Rule om du länkar kod som kompilerat mot två olika uppsättningar header-filer.

Jag tror att Bjarne glömde att ta med ett standardiserat ABI med flit.

Permalänk
Skrivet av Erik_T:

Knappast. Det finns ett standardiserat ABI för C på många plattformar. Man har helt enkelt bestämt att "På den här plattformen är int 32 bitar, long 32 bitar, pekare 64 bitar, osv". Och även bestämt hur funktionsnamn, variabelnamn, m.m. skall se ut i objektfiler och bibliotek, och bestämt hur argument och resultat skickas till/från funktioner. Med mera.

Jovisst, men du skriver både "ett standardiserat ABI för C" och "på många plattformar". Det är klart man vill vara interoperatibel, men detta är en sak som specas per platform, med storlekar, objektlayout och calling convention som passar den plattformen. Det är inte "ett standardiserat ABI för C". Hela poängen med mitt förra inlägg var att dylika plattformsdetaljer inte hör hemma i språkspecen eftersom det inte är ett one-size-fits-all-problem.

Skrivet av Erik_T:

Med resultat att man kan blanda filer kompilerade av olika kompilatorer.

#20982571

Permalänk
Medlem
Skrivet av Ingetledigtnamn:

Jovisst, men du skriver både "ett standardiserat ABI för C" och "på många plattformar". Det är klart man vill vara interoperatibel, men detta är en sak som specas per platform, med storlekar, objektlayout och calling convention som passar den plattformen. Det är inte "ett standardiserat ABI för C". Hela poängen med mitt förra inlägg var att dylika plattformsdetaljer inte hör hemma i språkspecen eftersom det inte är ett one-size-fits-all-problem.

Det finns nog inte ett standardiserat ABI för något språk - annat än sådana språk som per definition kör på en välspecifierad virtuell maskin.
Men det är rätt irrelevant, eftersom ett ABI mer eller mindre per definition är plattforms-specifikt.

C har ett välspecifierat ABI på de flesta plattformar. C++ har det inte - och det är främst på objektfilsnivå det skiter sig, inte i de hårdvaruspecifika bitarna. Men, att C++ inte har det har absolut ingenting att göra med att det är baserat på C.

Permalänk
Skrivet av Erik_T:

Det finns nog inte ett standardiserat ABI för något språk - annat än sådana språk som per definition kör på en välspecifierad virtuell maskin.
Men det är rätt irrelevant, eftersom ett ABI mer eller mindre per definition är plattforms-specifikt.

C har ett välspecifierat ABI på de flesta plattformar. C++ har det inte - och det är främst på objektfilsnivå det skiter sig, inte i de hårdvaruspecifika bitarna.

Jag förstår inte vad vi tjafsar om. Jag kommenterade

Skrivet av Yoshman:

... Att inte standardisera ABI är en "WTF Bjarne???".

Detta tolkar jag som att Bjarne borde ha spikat ett ABI som alla kompilatorer följde. Dvs, något som @Yoshman tyckte borde ha varit med i språkspecen. Den enda rimliga tolkningen är väl då att det skulle finnas ett ABI som alla kompilatorer använde? Specen kan ju inte lista alla plattformar som det kan tänkas skrivas C++-kompilatorer för, så enda rimliga tolkningen är väl ändå One ABI to rule them all? Tolkar du det på något annat sätt?

Problemet är inte avsaknaden av ABIer. Alla kompilatorer vi bryr oss om följer System V AMD64 ABI och Itanium C++ ABI för Linux. De har samma calling convention, samma objekt-layout och manglar på samma sätt. Problemet handlar snarare om att Itanium ABI lämnar lite luckor. Kompilatorerna har frihet att lösa detaljer på olika sätt och det ger i sin tur att koden kompilerad med kompilator A inte nödvändigtvis länkar med kompilator B. Den friheten måste de väl få ha? Eller är det "gör exakt samma sak som GCC" som gäller? I så fall, vad är det för poäng att ha olika kompilatorer?

Samma sak med biblioteksimplementationer. One Definition Rule ställer till det här. Ska alla kompilatorer på samma plattform behöva ha samma header-filer och samma biblioteksimplementation? Är det så vi vill ha det?

Skrivet av Erik_T:

Men, att C++ inte har det har absolut ingenting att göra med att det är baserat på C.

OK, vem är det som inte gjort sitt jobb ordentligt? Bjarne? Vad är det som saknas? Skulle detta ge länkkompatibilitet för alla plattformar?

Permalänk
Medlem
Skrivet av Ingetledigtnamn:

Jag förstår inte vad vi tjafsar om. Jag kommenterade

Vad jag tjafsar om är ditt påstående att

Citat:

Baseras språket på C, som inte specar hur lång en int skall vara, är det kört för ett standardiserat ABI.

som uppenbart inte är sant eftersom det finns standardiserade ABI:er för C.

Permalänk
Skrivet av Erik_T:

det finns standardiserade ABI:er för C.

Ok, om du läste det så ger jag mig. Det finns ABI:er för C. Det stämmer.

Dock tycker jag att det kanske borde ha framgått av de senare inläggen att jag inte argumenterade för att det skulle vara omöjligt att speca ABI:er för C, per plattform, utan för att det saknas ett ABI som alla C-kompilatorer, på alla plattformar, följer.

Permalänk
Medlem
Skrivet av klk:

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.

Är den inte snabbare? Har du testat? Eftersom jag inte programmerar i C++ och därför inte har någon intuition för vilka optimeringar som finns i standardbiblioteken och vilken kod som genereras av de olika kompilatorerna så tittade jag naturligtvis på den genererade assemblerkoden för att se om det gick att göra något smartare innan jag skrev min kod.

Just när det gäller SIMD så är det inte speciellt svårt att slå dagens kompilatorer. Autovektorisering har inga problem att generera bra kod för enklare problem och även lite mer komplicerade saker ihop med if-conversion. Däremot kan man använda olika trick om man känner till information/begräsningar om datan, vilket är omöjligt för kompilatorn. Ta parsning som exempel, för ascii kan man använda vpermi2b som en 128 element lookup-table där man kan göra 64 parallella uppslag. Man kan skriva snabbare kod för parsning av nummer om man vet att de bara kan innehålla en begränsat antal siffror osv.

En hel del SIMD optimeringar för generella strängar är redan implementerade för hand i standarbibliotek/JIT. Därför är det dumt att försöka skriva något eget om man inte har planer på att göra något ännu smartare.

Jag vet att @Yoshman redan har gjort ett försök för några veckor sedan men jag är fortfarande lite nyfiken på dina avx optimeringar. Du verkade ha rört ihop MIMD och SIMD och som svar på frågan "hur hacker du SIMD-koden idag?" kom det här:

Skrivet av klk:

SIMD är ganska ovanligt att skriva kod för enligt mig, det får processorn avgöra eftersom det krävs bra förutsättningar för att göra det och det tar ofta för lång tid att pilla med sådant.

Vad menar du med att du låter processorn avgöra? Kan du förklara lite tydligare hur det fungerar?

Permalänk
Medlem

För någon sida sedan diskuterade vi "reordering" sett från både kompilatorn och processorn. Jag ramlade på denna video som handlar om varför Petersons algoritm inte längre fungerar på moderna processorer. (Det är ett enkelt mutex-lock trick.) Hoppa till 13:05 om ni bara är intresserade av det vi har pratat om här i tråden eller 15:30 för enbart processorns hyss.
https://www.youtube.com/watch?v=QAzuAn3nFGo

PS. Jag berättade tidigare om en optimering jag kom på. Tricket är att nyttja dötid som all form av asynkrona avbrott skapar; då kan man initiera eller räkna ganska mycket medans användaren knackar in sitt filnamn eller om programmet gör ett nätverksanrop t.ex.

10 PRINT "Vilken fil vill du jobba med? " 20 REM Räkna så mycket du kan. ... 70 INPUT F$

Permalänk
Datavetare
Skrivet av Ingetledigtnamn:

Nja, att C inte har det problemet är kanske att ta i. GCC kan lägga ut anrop till hjälpfunktioner i libgcc så det är inte säkert att du kan länka GCC-genererad kod med clang rakt av om du valt att använda compiler-rt. På plattformar där clang använder GCCs bibliotek är detta inget som helst problem, men det beror på val som clang-implementatörerna gjort, inte på att C int ekan råka ut för liknande problem. Hur ser det ut på Windows? Där borde anropen till hjälpfunktionerna också kunna ställa till det.

Den tajta kopplingen mellan kompilator och dess bibliotek är olycklig, men om man krävde att all kod som genererats av kompilator A skulle kunna länkas mot kompilator Bs bibliotek skulle inte språkdefinitionen behöva speca allt då? Objektlayout, explicit med hur alla detaljer som virtuella baser, vtables, RTTI ska hanteras. Exception-mekanismen, inga Structured Exceptions på Windows Exakt hur alla templates skall vara definierade, inte bara layout utan även alla medlemsfunktioner. Annars riskerar du att bryta mot One Definition Rule om du länkar kod som kompilerat mot två olika uppsättningar header-filer.

Jag tror att Bjarne glömde att ta med ett standardiserat ABI med flit.

Inser att jag var allt för otydlig vad jag menade med "WTF...".

C-standarden specificerar mycket riktigt inte vilka register argument ska skickas i och sådana saker. Och precis som du nämnde, det är inte heller vettigt givet att det är en rätt hopplös uppgift att täcka alla varianter här.

Just den delen av ABI är det ju lämpligast att OS-leverantören specificerar. Eller i praktiken, OS-leverantören väljer ett av de ABI-specifikationer som redan finns. T.ex. Linux kör Arms AAPCS64 för ARM64 och System V Application Binary Interface
AMD64 för x86_64.

Och självklart kan man inte förvänta sig att något ska fungera om hälften är kompilerad med libc++ och hälften är kompilerat med libstdc++ (eller motsvarande för C).

Det man ändå borde ha spikat i C++, därför att det likt t.ex. endian inte spelar någon direkt roll vad man väljer bara man väljer samma, är saker som namngivning av symboler.

För det som specifikt misslyckades är det @Erik_T skriver ovan, då name-mangling är helt upp till kompilatorn (även om Clang och GCC verkar mestadels göra samma sak) så var det just det som gjorde att jag inte kunde kompilera applikationen med g++ (som jag explicit gav switchar så den körde med libc++ istället för libstdc++) mot google-benchark byggd med clang++ (och libc++).

Motsvarande har då under alla år jag jobbade med embedded fungerat helt utmärkt. På t.ex. PowerPC och Arm var det inte alls ovanligt att man hade förbyggda bibliotek kompilerade med någon specialkompilator, gick ändå utmärkt att länka det mot saker byggda med andra C-kompilatorer.

Sen är det knappast enda orsaken att C++ inte kunnat ersätta C inom en rad områden. Men det en av de viktiga orsakerna.

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 jclr:

Vad menar du med att du låter processorn avgöra? Kan du förklara lite tydligare hur det fungerar?

Felskrivning av mig, menade kompilatorn. Att kompilatorn får avgöra vad den väljer att optimera för SIMD för enligt mig är det för svårt för utvecklare idag. Endast där man vet att det är någon krävande operation som skall göras och det är viktigt för totalen samt att de har gott om pengar för att betala utvecklare, då kan det börja löna sig.

Och för att exemplifiera hur svårt det är. AMDs senaste processorer klarar AVX 512 med full hastighet. Så då kanske man tror att ok, då lägger jag in sådana operationer och får mycket snabbare kod. Det är inte alls säkert för om AVX skall fungera fullt ut måste data som används ligga i processorns cache. Ligger inte data i cachen behöver processorn vänta till data lästs och då kan det istället bli långsammare eftersom det är mer minne som behöver ligga i cachen.

ARMs logik skiljer sig från x86 för SIMD och är inte påläst hur det fungerar, skriver mest kod för x86 även om det blivit en del ARM senaste året. Läst att ARMs lösningen är flexibel, inte har fasta instruktioner beroende på vilken storlek på SIMD operation som väljs och det borde kanske x86 titta närmare på.

Permalänk
Medlem
Skrivet av mc68000:

För någon sida sedan diskuterade vi "reordering" sett från både kompilatorn och processorn. Jag ramlade på denna video som handlar om varför Petersons algoritm inte längre fungerar på moderna processorer. (Det är ett enkelt mutex-lock trick.) Hoppa till 13:05 om ni bara är intresserade av det vi har pratat om här i tråden eller 15:30 för enbart processorns hyss.
https://www.youtube.com/watch?v=QAzuAn3nFGo

Tack, mycket bra video

Permalänk
Skrivet av Yoshman:

Det man ändå borde ha spikat i C++, därför att det likt t.ex. endian inte spelar någon direkt roll vad man väljer bara man väljer samma, är saker som namngivning av symboler.

För det som specifikt misslyckades är det @Erik_T skriver ovan, då name-mangling är helt upp till kompilatorn (även om Clang och GCC verkar mestadels göra samma sak) så var det just det som gjorde att jag inte kunde kompilera applikationen med g++ (som jag explicit gav switchar så den körde med libc++ istället för libstdc++) mot google-benchark byggd med clang++ (och libc++).

Motsvarande har då under alla år jag jobbade med embedded fungerat helt utmärkt. På t.ex. PowerPC och Arm var det inte alls ovanligt att man hade förbyggda bibliotek kompilerade med någon specialkompilator, gick ändå utmärkt att länka det mot saker byggda med andra C-kompilatorer.

Sen är det knappast enda orsaken att C++ inte kunnat ersätta C inom en rad områden. Men det en av de viktiga orsakerna.

Jag tycker också att det är opraktiskt att man inte kan distribuera bibliotek i objektkodsformat och länka med valfri kompilator men jag hävdar fortfarande att språkspecen inte skall detaljstyra saker som har med implementation att göra.

Specen är det "publika API:t", den talar om vad som skall hända. Den skall inte tala om hur det skall lösas, det är upp till verktygskedjan att lösa. På samma sätt som att specen talar om vad exempelvis strcpy skall göra, inte hur implementation skall kopiera strängen, skall specen heller inte detaljstyra saker som den inte måste. Ju mer den styr desto mindre frihet har implementatörerna.

Anledningen till att vi har mangling är att Bjarne var tvungen att mappa ner symbol + typsignatur till något som gick att uttrycka i C och fungerade med dåtidens länkare. Den modellen sitter vi fortfarande fast i. Är det den optimala lösningen på en 1-to-many-mappning för symboler? Jag kan tänka mig att en modell som kunde uttrycka scope-hierarkin för en symbol vore lättare att jobba med. Exempelvis IAR valde att utöka sitt objektfilsformat med typsignaturer och hade symbolerna i klartext. Bjarne skriver själv (The Design and Evolution of C++, Ch 4.5 Low-level Programming Support Rules):

Several features of C++, notably type-safe linkage and templates, can be implemented using traditional linkers, but they can be implemented better with more linker support. A secondary aim has been for C++ to provide stimulus to improved linker design.

Om specen stipulerar hur manglingen skall ske är det tvingade, då måste alla verktygskedjor mangla symbolerna, och möjligheten att välja andra bättre lösningar försvinner. Skall man tvinga alla att använda samma lösning, då skall man väl inte ta en som språkets upphovsman tycker är mindre bra?

Med detta sagt, man skulle kunna ha rekommendationer för hur manglingen skulle ske, men vore det tillräckligt för att saker skulle fungera?

Det skulle sannolikt länka, men skulle det fungera? Om du har kompilerat mot två olika template-implementationer riskerar du att få outline-kopior av godtyckliga funktioner med WEAK linkage. Enhetlig mangling skulle säkerställa att funktioner med samma signatur skulle manglas likadant och länkaren kommer välja att ta med en av alla WEAK-funktioner med samma namn. Vad händer om de inte gjorde exakt samma sak i de båda template-biblioteken? Vore det bättre om koden länkade och vi fick subtila buggar för att funktionerna inte gör det som biblioteksimplementatören förväntade sig? ODR skall skydda oss mot detta, men hur skall länkaren säkerställa att den upprätthålls? Jag tror att det faktiskt är bättre att koden inte länkar.

Permalänk
Medlem
Skrivet av klk:

Felskrivning av mig, menade kompilatorn. Att kompilatorn får avgöra vad den väljer att optimera för SIMD för enligt mig är det för svårt för utvecklare idag. Endast där man vet att det är någon krävande operation som skall göras och det är viktigt för totalen samt att de har gott om pengar för att betala utvecklare, då kan det börja löna sig.

Så när du skriver "Har själv gjort en del optimeringar med avx" så menar du att du låtit kompilatorn autovektorisera koden?

Att det var en felskrivning är inte uppenbart med tanke på:

Skrivet av klk:

För att simd instruktioner skall fungera måste minnet ligga i cache. Jag tror det är ett krav och allt måste ligga där. Ligger det inte i cache fungerar inte operationen och då går den ner till att tugga varje instruktion var för sig.

Här antyder du att processorn på något sätt kör andra instruktioner om "minnet" inte ligger i cachen?

Skrivet av klk:

Och för att exemplifiera hur svårt det är. AMDs senaste processorer klarar AVX 512 med full hastighet. Så då kanske man tror att ok, då lägger jag in sådana operationer och får mycket snabbare kod. Det är inte alls säkert för om AVX skall fungera fullt ut måste data som används ligga i processorns cache. Ligger inte data i cachen behöver processorn vänta till data lästs och då kan det istället bli långsammare eftersom det är mer minne som behöver ligga i cachen.

På vilket sätt behöver mer minne ligga i cachen?

Det verkar som du har en del missuppfattningar om hur cache och minnessystemet fungerar.
Om vi tar ett enklare exempel utan att tänka på simd. Vi tar en modern x86. Du har en Array med 32 bitars integers (aligned så du kan ignorera split load/store). Arrayen finns inte i någon cache. Du utför en instruktion data[3] = 42; Hur många bytes måste processorn LÄSA från minnet?

Permalänk
Medlem
Skrivet av jclr:

Här antyder du att processorn på något sätt kör andra instruktioner om "minnet" inte ligger i cachen?

Inte andra utan måste vänta på att processorn hämtar minnet från ram innan den kan utföra AVX operationen.

Skrivet av jclr:

På vilket sätt behöver mer minne ligga i cachen?

För att dra nytta av hastigheten AVX har, långsamt att hämta data från ram

Skrivet av jclr:

Arrayen finns inte i någon cache. Du utför en instruktion data[3] = 42; Hur många bytes måste processorn LÄSA från minnet?

64 byte

Permalänk
Medlem
Skrivet av klk:

Inte andra utan måste vänta på att processorn hämtar minnet från ram innan den kan utföra AVX operationen.

"Ligger det inte i cache fungerar inte operationen och då går den ner till att tugga varje instruktion var för sig." Det är varje instruktion var för sig påståendet som jag undrade om du kunde förtydliga?

Skrivet av klk:

För att dra nytta av hastigheten AVX har, långsamt att hämta data från ram

64 byte

Då är vi överens om att den minsta enheten man arbetar med mot minnet är en cacheline som i x86 fallet är 64B. Det spelar alltså ingen roll att det bara är 4 byte man vill skriva utan man måste först läsa in hela cachelinen på 64B även fast man inte är intressad av de övriga 60B.

Om vi nu tar en AVX512/10.2 instruktion som arbetar med 64B åt gången på samma array. Hur menar du då att "det är mer minne som behöver ligga i cachen" fungerar?

Permalänk
Medlem
Skrivet av jclr:

"Ligger det inte i cache fungerar inte operationen och då går den ner till att tugga varje instruktion var för sig." Det är varje instruktion var för sig påståendet som jag undrade om du kunde förtydliga?

Om en AVX instruktion skall kunna utföras behöver all data vara inläst i register och för att processorn skall kunna läsa in datat i registren så snabbt som möjligt behöver data ligga i cache. Ligger inte data i cache fungerar inte de optimeringar som processorn har för det här och då blir det problem.
Exempel: En avx instruktion som kör på 256 bit (32 byte), där är sannolikheten kanske högre att all data ligger i cache jämfört med att det dubbla ligger i cache och om så är fallet kommer avx 256 gå snabbare än avx 512. Är det mycket data så kommer självklart avx 512 bättre till sin rätt men ofta är det ju småblock med minne man jobbar med och där kan det vara dumt och avx optimera på grund av ovanstående fenomen.

Skrivet av jclr:

Då är vi överens om att den minsta enheten man arbetar med mot minnet är en cacheline som i x86 fallet är 64B. Det spelar alltså ingen roll att det bara är 4 byte man vill skriva utan man måste först läsa in hela cachelinen på 64B även fast man inte är intressad av de övriga 60B.

Ja men datat måste ligga i cache line, och för att det skall hamna där snabbt behöver det ligga i cache, ligger inte data in cache line behöver du ut till ram och där går det inte lika snabbt. Jag antar att du menar att data skulle vara perfekt alignat för 64 bytes cache lines, skall kod vara det behöver den skrivas för det.

Perfekt anpassad kod för AVX 512 är självklart ideala, det behöver programmerare fixa isåfall

Permalänk
Datavetare
Skrivet av mc68000:

För någon sida sedan diskuterade vi "reordering" sett från både kompilatorn och processorn. Jag ramlade på denna video som handlar om varför Petersons algoritm inte längre fungerar på moderna processorer. (Det är ett enkelt mutex-lock trick.) Hoppa till 13:05 om ni bara är intresserade av det vi har pratat om här i tråden eller 15:30 för enbart processorns hyss.
https://www.youtube.com/watch?v=QAzuAn3nFGo

För den som av någon märklig anledning vill använda Peterson algoritm 2025, denna borde vara korrekt även på moderna CPUer utan att ha några onödiga fence

#include <atomic> #include <iostream> #include <thread> #define ITERATIONS 1'000'000 // Use of volatile prevents the compiler from optimize away read/writes // and prevent compiler from re-order read/writes with respect to other volatiles static volatile bool want[2]{false, false}; static volatile int turn = 0; static volatile int shared_counter = 0; void lock(int id) { int other = 1 - id; want[id] = true; turn = other; // Emits a full fence on all CPUs std::atomic_thread_fence(std::memory_order_seq_cst); for (;;) { if (!want[other]) { break; } // Does not emit anything on CPUs using TSO, like x86 std::atomic_thread_fence(std::memory_order_acquire); if (turn != other) { break; } } } void unlock(int id) { // Does not emit anything on CPUs using TSO, like x86 std::atomic_thread_fence(std::memory_order_release); want[id] = false; } void peterson_critical_section(int id, int iters) { int other = 1 - id; for (int k = 0; k < iters; ++k) { lock(id); shared_counter = shared_counter + 1; unlock(id); } } int main() { std::thread t0(peterson_critical_section, 0, ITERATIONS); std::thread t1(peterson_critical_section, 1, ITERATIONS); t0.join(); t1.join(); std::cout << "shared_counter = " << shared_counter << "\n"; << " (expected " << 2 * ITERATIONS << ")\n"; }

Dold text

Att någon så här i grunden simpelt blir så komplex bör trigga tanken "nog best att använda de lite mer högnivåsync-mekanismer som finns alt. använda sig av ramverk som sköter detta".

Han började sväva ut lite i spenaten i slutet av videon. Cache har inget med detta att göra, undantaget ett buggigt koherens-protokoll. Däremot har alla förutom de absolut enklaste CPUerna någon form av buffring på minnesoperationer, framförallt på skrivning, och DET är problemet.

Visa signatur

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

Permalänk
Datavetare
Skrivet av Ingetledigtnamn:

Jag tycker också att det är opraktiskt att man inte kan distribuera bibliotek i objektkodsformat och länka med valfri kompilator men jag hävdar fortfarande att språkspecen inte skall detaljstyra saker som har med implementation att göra.

Specen är det "publika API:t", den talar om vad som skall hända. Den skall inte tala om hur det skall lösas, det är upp till verktygskedjan att lösa. På samma sätt som att specen talar om vad exempelvis strcpy skall göra, inte hur implementation skall kopiera strängen, skall specen heller inte detaljstyra saker som den inte måste. Ju mer den styr desto mindre frihet har implementatörerna.

Anledningen till att vi har mangling är att Bjarne var tvungen att mappa ner symbol + typsignatur till något som gick att uttrycka i C och fungerade med dåtidens länkare. Den modellen sitter vi fortfarande fast i. Är det den optimala lösningen på en 1-to-many-mappning för symboler? Jag kan tänka mig att en modell som kunde uttrycka scope-hierarkin för en symbol vore lättare att jobba med. Exempelvis IAR valde att utöka sitt objektfilsformat med typsignaturer och hade symbolerna i klartext. Bjarne skriver själv (The Design and Evolution of C++, Ch 4.5 Low-level Programming Support Rules):

Several features of C++, notably type-safe linkage and templates, can be implemented using traditional linkers, but they can be implemented better with more linker support. A secondary aim has been for C++ to provide stimulus to improved linker design.

Om specen stipulerar hur manglingen skall ske är det tvingade, då måste alla verktygskedjor mangla symbolerna, och möjligheten att välja andra bättre lösningar försvinner. Skall man tvinga alla att använda samma lösning, då skall man väl inte ta en som språkets upphovsman tycker är mindre bra?

Med detta sagt, man skulle kunna ha rekommendationer för hur manglingen skulle ske, men vore det tillräckligt för att saker skulle fungera?

Det skulle sannolikt länka, men skulle det fungera? Om du har kompilerat mot två olika template-implementationer riskerar du att få outline-kopior av godtyckliga funktioner med WEAK linkage. Enhetlig mangling skulle säkerställa att funktioner med samma signatur skulle manglas likadant och länkaren kommer välja att ta med en av alla WEAK-funktioner med samma namn. Vad händer om de inte gjorde exakt samma sak i de båda template-biblioteken? Vore det bättre om koden länkade och vi fick subtila buggar för att funktionerna inte gör det som biblioteksimplementatören förväntade sig? ODR skall skydda oss mot detta, men hur skall länkaren säkerställa att den upprätthålls? Jag tror att det faktiskt är bättre att koden inte länkar.

Är helt med på att det finns historiska orsaker att det Bjarne en gång i tiden gjorde var vettigt DÅ.

Ändrar inte att det är en i raden av saker som har ställt till problem senare, problem som andra språk/miljöer inte har.

Grävde lite mer i detta och visar sig att det uppenbarligen är fullt möjligt att producera binär-kompatibla C++-bibliotek om man faktiskt vill. Det finns ABI:er som inkluderar saker som name-manging, stack-unwiding vid exceptions etc.

Men verkar i nuläget bara vara Microsoft/Windows som orkar brytt sig implementera det fullt ut, där ska kod kompilerad med MSVC++ vara möjligt och kombinera med clang-cl (en variant av clang++ som är konfigurerad att köra med Windows C++ standardbibliotek).

På MacOS var det rätt sannolikt att man skulle gå på en mina, även om just name-mangling mestadels är lika på clang++ och g++ där, men fanns andra problem.

På Linux används en ABI som kallas Itanium C++ ABI. Ska tydligt vara högst sannolikt att man kan länka ihop saker byggda med g++ och clang++. Det ska tydligen också fungera nästan i alla lägen.

Att C++ kompilatorer inte är kompatibla med varandra har ju hindrat användning av bibliotek som exponerar ett C++ API. De gånger jag jobbat med C++ i bibliotek har det alltid varit ett C API som exponerats. Givet denna tråd och vad Bjarne ibland gnäller om (folk envisas med att skriva gammal C++ alt. C) så är det också något som ligger C++ i fatet idag.

Det skrivet: varken Rust eller Go är binär-kompatibelt mellan sina "normal" toolchains och deras GNU-toolchains. Så C++ är inte sämre en dessa, fast å andra sidan har Go "duckat" det problem lite då en orsak att det är populärt i "molnet" är att gobinärer byggs till en self-contained binär (inga dll/so-filer), vilket är väldigt trevligt när man ska göra "deploy" (trots rätt stora försök från Microsoft att undvika dll-hell i .NET blir det ibland just dll-hell vid deploys där...).

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:

Om en AVX instruktion skall kunna utföras behöver all data vara inläst i register och för att processorn skall kunna läsa in datat i registren så snabbt som möjligt behöver data ligga i cache. Ligger inte data i cache fungerar inte de optimeringar som processorn har för det här och då blir det problem.
Exempel: En avx instruktion som kör på 256 bit (32 byte), där är sannolikheten kanske högre att all data ligger i cache jämfört med att det dubbla ligger i cache och om så är fallet kommer avx 256 gå snabbare än avx 512. Är det mycket data så kommer självklart avx 512 bättre till sin rätt men ofta är det ju småblock med minne man jobbar med och där kan det vara dumt och avx optimera på grund av ovanstående fenomen.

Ja men datat måste ligga i cache line, och för att det skall hamna där snabbt behöver det ligga i cache, ligger inte data in cache line behöver du ut till ram och där går det inte lika snabbt. Jag antar att du menar att data skulle vara perfekt alignat för 64 bytes cache lines, skall kod vara det behöver den skrivas för det.

Perfekt anpassad kod för AVX 512 är självklart ideala, det behöver programmerare fixa isåfall

Visst måste data ligga i data-cachen för maximal prestanda, men det är oftast ett betydligt mindre problem än du gör det till.

Första gången du skall använda någon viss data så ligger det inte i cachen utan behöver läsas in från minnet. Det gäller ju oavsett hur data är alignat, eller vad du skall ha det till.

Är det data du använder ofta, och det inte är alltför stora datamängder, så kommer processorn se till att datat med stor sannolikhet ligger i cachen kommande gånger du använder den datan. Är det riktigt stora mängder data så får man använda lämpliga tekniker för att bara jobba på en del av datan i taget så att working-set får plats i cachen.

Är det data du inte använder ofta, ja då spelar ju en cache-miss eller två när du väl skall använda det inte så stor roll.

Att ha all data perfekt alignat spelar inte heller så väldigt stor roll. Som följd av hur moderna minneskretsar fungerar så tar det normalt inte så mycket längre tid att läsa två konsekutiva cache-linjer från minnet än att läsa bara en.
Och om all data redan ligger i L1 cachen, så tar det bara marginellt mer tid att fylla ett register med data från två cache-linjer jämfört med en.

Permalänk
Medlem
Skrivet av Erik_T:

Visst måste data ligga i data-cachen för maximal prestanda, men det är oftast ett betydligt mindre problem än du gör det till.

Hur tänker du nu?
Jag har ju åkt på massiv kritik för att skriva optimerad kod, bl.a. kod som passar för avx. Det jag tidigare hört är att man inte behöver tänka på att skriva optimerad kod för om man skall göra något som fungerar för avx512 då bör man tänka på hur data ser ut i koden, annars kommer processorn inte kunna använda operationer för cache lines. Det finns en del annat i avx512 så det kan självklart användas.

Skrivet av Erik_T:

Första gången du skall använda någon viss data så ligger det inte i cachen utan behöver läsas in från minnet. Det gäller ju oavsett hur data är alignat, eller vad du skall ha det till.

Kanske men avx är bäst på stora minnesblock eller i alla fall block av data som dras igenom och de ligger inte alltid i cache, tror det är vanligt att program som skall jobba på sådana block förladdar.
Data som normalt ligger i cache brukar vara av "spretigare" art om man kan beskriva det så. Hitta stora block där samma operation utförs på alla värden är speciellt.

Permalänk
Skrivet av klk:

Hur tänker du nu?
Jag har ju åkt på massiv kritik för att skriva optimerad kod, bl.a. kod som passar för avx. Det jag tidigare hört är att man inte behöver tänka på att skriva optimerad kod för om man skall göra något som fungerar för avx512 då bör man tänka på hur data ser ut i koden, annars kommer processorn inte kunna använda operationer för cache lines. Det finns en del annat i avx512 så det kan självklart användas.

Kom inte du och @jclr överens om att CPU accessar minne som hela cache-lines, 64 bytes? Varför skulle en AVX512-instruktion vara mer känslig för om operanden redan finns i cache än vanliga instruktioner? I värsta fall kommer din minnesoperand vara oalignad och ligga delad i två olika cache lines, men kan hända med oalignade word, dword och qword-accesser också. CPUn behöver hämta exakt lika mycket data från minne i båda fallen.

Permalänk
Medlem
Skrivet av Ingetledigtnamn:

Kom inte du och @jclr överens om att CPU accessar minne som hela cache-lines, 64 bytes? Varför skulle en AVX512-instruktion vara mer känslig för om operanden redan finns i cache än vanliga instruktioner? I värsta fall kommer din minnesoperand vara oalignad och ligga delad i två olika cache lines, men kan hända med oalignade word, dword och qword-accesser också. CPUn behöver hämta exakt lika mycket data från minne i båda fallen.

Om jag frågar dig så här istället

Nu är max AVX512, fyrdubbla det till AVX2048, skulle det alltid vara bättre eller varför inte dubbla igen till avx4096

Permalänk
Medlem
Skrivet av klk:

Ligger inte data i cache fungerar inte de optimeringar som processorn har för det här och då blir det problem.

Om det här var ett svar på "Ligger det inte i cache fungerar inte operationen och då går den ner till att tugga varje instruktion var för sig." så är det fortfarande lika otydligt. Att processorer ofta får vänta minne gäller ju oavsett simd eller inte men du antyder hela tiden att det är operationer och optimeringar som inte fungerar? Det är samma sak i svaret till @Erik_T "…annars kommer processorn inte kunna använda operationer för cache lines."?

Skrivet av klk:

Exempel: En avx instruktion som kör på 256 bit (32 byte), där är sannolikheten kanske högre att all data ligger i cache jämfört med att det dubbla ligger i cache och om så är fallet kommer avx 256 gå snabbare än avx 512.

Hur tänker du nu? Om jag gör en 64B load så är det för att jag vill komma åt alla 64B. På vilket sätt skulle 32B loads öka sannorlikheten att datan finns i cache?

[----------------|----------------] minne i 64B block ****** ********** array med 16 ints 111111 1122222222 två 256 bit loads 111111 1111111111 en 512 bit load

Skrivet av klk:

Är det mycket data så kommer självklart avx 512 bättre till sin rätt men ofta är det ju småblock med minne man jobbar med och där kan det vara dumt och avx optimera på grund av ovanstående fenomen.

"man" Du har valt ett sätt att arbeta med minne med dina tagged-unions som är simd-fientligt.

Permalänk
Medlem
Skrivet av jclr:

Hur tänker du nu? Om jag gör en 64B load så är det för att jag vill komma åt alla 64B. På vilket sätt skulle 32B loads öka sannorlikheten att datan finns i cache?

Förutom load, vad är det mer för villkor som måste uppfyllas för att du skall kunna köra simd-operationer?

Skrivet av jclr:

"man" Du har valt ett sätt att arbeta med minne med dina tagged-unions som är simd-fientligt.

ok. du tror det. Så vad är simd-vänligt enligt dig?

Skulle du kunna visa exempel på kod där simd fungerar bra

Permalänk
Skrivet av klk:

Om jag frågar dig så här istället

Nu är max AVX512, fyrdubbla det till AVX2048, skulle det alltid vara bättre eller varför inte dubbla igen till avx4096

En klassisk "Här vet jag inte vad jag skall svara så jag drar till med en motfråga istället".

Det korta svaret är: Nej. Det skulle inte alltid vara bättre. Du är lite oklar i din frågeställning, eller snarare, inte tillräckligt specifik. Det finns lite olika sätt man skulle kunna implementera AVX2048 på.

Ett alternativ vore att behålla den nuvarande arkitekturen med 512-bits register, låta 2048-bits operationerna operera på tupler av register, exempelvis vaddps zmm0-3, zmm16-19, zmm24-27, och köra mikrokod som utförde 4 512-bits operationer under huven. Skulle det bli bättre? Nej. Beräkningskraftsmässigt ligger du på samma nivå som AVX512, du har bara infört en ny kompaktare form att uttrycka saker som du lika gärna skulle kunna skrivit som AVX512-instruktioner och för AVX512 är det osannolikt att det skulle vara front ends förmåga att avkoda nya instruktioner som är den begränsande faktorn i en beräkning.

Ett annat alternativ vore att vidga registren till 2048 bitar. Det skulle vara kostsamt, i kiselyta, att bredda register, datavägar och ALUer. Skulle det alltid vara bättre? Nej. Du fyrdubblar den teoretiska beräkningskraften men redan i dagsläget är det ofta svårt att hålla pipelinen fylld för AVX512 då minnesbandbredden inte räcker till. Om du gör mycket register till register-beräkningar skulle du kunna tjäna på att bredda instruktionerna till 2048 bitar men detta skall vägas mot att du samtidigt konsumerar 4X mer data per instruktion. Jag tror det är få uppgifter som skulle tjäna på att bredda till 2048 bitar.

För att återknyta till den ursprungliga frågan så skulle AVX2048-instruktioner, som givet dagens cache-arkitektur skulle behöva 4 cache lines för en eventuell minnesoperand, vara känsliga för om data finns i cache eller inte. Men om alternativet är att hämta samma data med skalär-instruktioner så skulle det inte gå långsammare att köra det som en AVX2048-instruktion.

Som kuriosa kan vi nämna att X86 har en väldigt specialiserad utökning, AMX, som gått ett steg till. Där är tile-registren 8192 bitar breda. Dessa är register är dock bara där för att pumpa data in i en systolisk array så deras enda användningsområde är matrismultiplikation.