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

Permalänk
Medlem
Skrivet av Yoshman:

En FYI: autovektorisering kommer nästan aldrig kicka in för flyttal då de inte är associativa, vilket betyder att resultatet kan bli annorlunda från fallet om man följer C++ specifikationen. Så en kompilator är då inte tillåten att göra en sådan optimering, det vore en bug.

Det brukar finnas flaggor för att runda sådant sökte snabbt och -ffast-math, /fp:fas (MSVC) ser ut och strunta i avrundningsfel, finns en del #pragma med, inte kollat upp nu men det skulle förvåna om det inte går.

Skrivet av Yoshman:

Det går att säga åt kompilatorn att förutsätta att flyttal följer matematikens regler med -ffast-math flaggan.

precis

Skrivet av Yoshman:

Precis som @jclr skriver ovan kompileras den inre loopen i din "SIMD-vänliga kod" till detta

Koden jag skrev var för att visa principen, att "ordna data". Har inte testat just det här för att se om det går att också få den vektoriserad men det finns en hel del i C++ som man kan ta till för att berätta för kompilatorn att det är ok att vectorisera koden och även om det inte skulle gå idag, blir kompilatorer bättre och bättre.

Har prövat en hel del annan kod och den normala reaktionen är att man blir förvånad över hur duktig kompilatorn är på att snabba upp koden, inte det motsatta.

Enligt mig är det inte en bra strategi att välja tekniker där man i princip kan räkna ut att logiken snart finns direkt i språket. Har man gjort den tabben några gånger gör man den inte igen.

Skrev nära nog ett helt eget stl mellan 2001-2004 ungefär (om jag minns rätt) och orsaken var att stl då var segt, exempelvis har jag för mig att std::string inte lagrade text med extra utrymme, varenda förändring allokerade om till en ny sträng. Microsoft hade flera år tidigare släppt MFC som hade sin CString med referensräknare. Den var då mycket snabbare än stl kod. Så tog ideer från MFC och lite annat och gjorde något som passade stl. STL gick alltså inte att använda något som behövde minsta lilla snabbhet.
Saker som "move" konstruktörer som gjort livet enklare för utvecklare vad ju ett stort problem då

Jämför med stl idag, det är nästan som att jämföra python med C++

Exemeplvis är AVX512 så nytt (tänker då på att dra nytta av det med full hastighet) så det kommer säkert ta ett tag innan annat hinner ikapp och när det andra hunnit ikapp finns det nya saker igen.

Permalänk
Medlem
Skrivet av Yoshman:

Är fortfarande inte med på vad din rätt komplicerade tabell skulle tillföra.

Uteckliingshastighet
Det är arkitektonisk lösning för att få upp snabbhet/produktivitet och det jag tidigare kallat för DOD (data orienterad design), om det heter något annat så vet jag inte hur jag skall benämna det
I alla fall så använder du också kod i dina exempel även om det då begränsar sig till standard C++ och det är givetvis en fördel i en tråd som denna. Men det är inte samma sak som att det inte går att göra smidigare.

För de som jobbat en del med arkitektur märker säkert snabbt hur lätt olika typ av kod är att förändra för det är viktigt.
Låt säga att du i ditt exempel plötsligt behöver en sträng också, eller kanske andra typer av tal, då har du problem och får skriva om koden en del.

Permalänk
Medlem

Du har inte visat principen för det vektoriserades inte?

Permalänk
Medlem
Skrivet av pine-orange:

Du har inte visat principen för det vektoriserades inte?

Kan du utveckla?

Pröva och skriv kod i compiler explorer och testa med olika flaggor så ser du hur olika koden blir.
Personligen behöver koden jag gör fungera på ARM och x86 (kanske annan cpu med) så behöver tänka på det.

Det här tog mycket tid förr innan AI men sådant här har blivit mycket smidigare

Permalänk
Datavetare
Skrivet av klk:

Uteckliingshastighet
Det är arkitektonisk lösning för att få upp snabbhet/produktivitet och det jag tidigare kallat för DOD (data orienterad design), om det heter något annat så vet jag inte hur jag skall benämna det
I alla fall så använder du också kod i dina exempel även om det då begränsar sig till standard C++ och det är givetvis en fördel i en tråd som denna. Men det är inte samma sak som att det inte går att göra smidigare.

För de som jobbat en del med arkitektur märker säkert snabbt hur lätt olika typ av kod är att förändra för det är viktigt.
Låt säga att du i ditt exempel plötsligt behöver en sträng också, eller kanske andra typer av tal, då har du problem och får skriva om koden en del.

Kan fortfarande inte på något sätt se poängen.

Om man nu vill kunna spara t.ex. int, double, std::string är det ju trivialt.

... using Element = std::variant<int, double, std::string>; using Row = std::array<Element, TotalCols>; ...

STL kan ha känts lite icke-optimerat i de tidiga implementationerna, men då pratar vi 90-tal och möjligen 00-tal. De varianter som finns idag är väldigt väloptimerade. MSVC++, GCC och Clang/LLVM har alla dedikerade team som specifikt jobbar med standardbiblioteken.

Skrivet av klk:

Kan du utveckla?

Pröva och skriv kod i compiler explorer och testa med olika flaggor så ser du hur olika koden blir.
Personligen behöver koden jag gör fungera på ARM och x86 (kanske annan cpu med) så behöver tänka på det.

Det här tog mycket tid förr innan AI men sådant här har blivit mycket smidigare

När det kommer till autovektorisering ihop med GCC/Clang finns i praktiken inget alls som skiljer sig mellan Arm, x86, RISC-V eller vad man nu kompilerar till. Genesis till Intels SPMD var observationen att Clangs IL hade sedan länge väldigt bra stöd för SIMD (bl.a. då man har stöd för GPUer), men det är väldigt svårt att uttrycka logik i de "normala" språken på ett sätt som passar SIMD väl.

För att autovektorisering ska kicka in är det en rätt lång rad av villkor som måste vara uppfyllda. Även när alla stjärnorna står i linje är det ändå rätt rudimentärt stöd man får. Autovektorisering är trevligt just då det är automatiskt, men det går inte att jämföra med vad som är möjligt med manuella metoder (intrinsics/assembler) eller ramverk specifikt designade för SIMD (t.ex. SPMD och SyCL).

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:

Om man nu vill kunna spara t.ex. int, double, std::string är det ju trivialt.

Det är inte trivialt men om man tror att program endast består av några hundra rader kod så är det trivialt. Program består inte av några hundra rader och speciellt sådan här typ av kod som inte har färdiggjorda ramverk. Att utvecklare själva måste designa koden, skillnaden är ENORM.
Din "special" kod finns bara i ditt huvud, andras lösningar finns i deras huvud. När ni börjar närma er kanske 50 000 rader där varje kodare skriver sina speciallösningar så tar stopp, under tiden har arbetet gått långsammare och långsammare.

Att så många utvecklare ändå tror att sådant här är "trivialt" är att de vanligen sitter med färdiga ramverk och har en fast arkitektur. De behöver bara följa den, inte lära sig själva varför.

Smart C++ med massa stl och templates skrivna av en programmerare som fått feeling och börjar lära sig alla coola trick, den koden blir med nära nog 100% säkerhet endast något som kommer leva programmerarens huvud. Snart bli det också tråkigt när utvecklare måste refaktorera för att man glömt saker som koden behövde fixa.
Och så dyker buggar upp, koden blir svårare och svårare att hantera.

Diskussionen om HN handlar bl.a. om det här, och det är en av alla tekniker som används för att klara av att skala stora mängder kod.

Permalänk
Datavetare
Skrivet av klk:

Det är inte trivialt men om man tror att program endast består av några hundra rader kod så är det trivialt. Program består inte av några hundra rader och speciellt sådan här typ av kod som inte har färdiggjorda ramverk. Att utvecklare själva måste designa koden, skillnaden är ENORM.
Din "special" kod finns bara i ditt huvud, andras lösningar finns i deras huvud. När ni börjar närma er kanske 50 000 rader där varje kodare skriver sina speciallösningar så tar stopp, under tiden har arbetet gått långsammare och långsammare.

Att så många utvecklare ändå tror att sådant här är "trivialt" är att de vanligen sitter med färdiga ramverk och har en fast arkitektur. De behöver bara följa den, inte lära sig själva varför.

Smart C++ med massa stl och templates skrivna av en programmerare som fått feeling och börjar lära sig alla coola trick, den koden blir med nära nog 100% säkerhet endast något som kommer leva programmerarens huvud. Snart bli det också tråkigt när utvecklare måste refaktorera för att man glömt saker som koden behövde fixa.
Och så dyker buggar upp, koden blir svårare och svårare att hantera.

Diskussionen om HN handlar bl.a. om det här, och det är en av alla tekniker som används för att klara av att skala stora mängder kod.

Fast är väl ändå din hemma-kokade lösning som är "specialkoden"? Exemplen jag givet är just exempel på hur man enkelt kan ta det som redan finns i standardbiblioteket. Det som är långt ifrån något specialfall, utan används typiskt överallt som bas till små som gigantiska projekt.

Vad och hur man jobbar med algoritmer, containers, etc i standardbiblioteket kan ju "alla C++ programmerare", så låg mental last att läsa koden vilket är kritiskt för stora kodbaser. Samtidigt kan man utnyttja sättet C++ hanterar generics till att inte ha mer komplexa element-typer än vad varje enskilt fall kräver för stunden.

I majoriteten av fallen använder man då bara template. För ja, egen template-meta-programming blir snabbt helt obegripligt och något man i de flesta fall bör undvika.

Så delen jag inte alls greppar är: vad tillför dina tabeller som inte rätt enkelt kan göras med standard-containers + t.ex. std::variant?

Sist: 50k rader är väl ändå ett rätt litet projekt? Väldigt få av de C och C++ projekt har varit mindre än så.

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:

Fast är väl ändå din hemma-kokade lösning som är "specialkoden"?

Hur menar du att din lösning inte var "hemma-kokad" ?

Ta följande sekvens

for (std::size_t i = 0; i < Rows; ++i) { std::cout << '|'; for (std::size_t j = 0; j < TotalCols; ++j) { std::cout << std::setw(5) << static_cast<int>(tbl[i][j]) << " |"; } std::cout << '\n'; }

Förstår att du ville göra något liknande, inte skriva en färdig lösning. Så helt ok, denna hade ju direkt hamnat i sopsäcken för normala lösningar.

Men vad jag inte förstår är att man inte ser vinsten i att kunna återanvända.

Klistrar in en bild här för att visa andra format som kan skriva ut tabellen. Aktuellt fall handlar om hjälptext för terminal applikation. Om text måste få plats inom en viss bredd behöver logik skrivas för att bryta texten. Räkna ut hur många rader texten behöver. Inte nog med det utan de andra kolumnerna behöver också förlängas med samma antal rader.
Det här tar en del tid att skriva kod för och den tiden hade inte investerats i en lösning som inte är återanvändbar, då exploderar koden i storlek och går inte hantera.
Kan man däremot skriva återanvändbar logik är investerad tid mycket bättre använd för plötsligt har man en metod som kan användas lite överallt.

Programmerare som vet att skriva återanvändbar kod skriver säkrare kod och är mycket snabbare än de som hårdkodar

Skrivet av Yoshman:

Sist: 50k rader är väl ändå ett rätt litet projekt? Väldigt få av de C och C++ projekt har varit mindre än så.

Och då jobbar du också i en inarbetat arkitektur, antingen har det funnits utvecklare som vet hur designar kod eller så har de använt något färdigt. Att nå +50 000 rader kod med att skriva likt exempelkod som man kanske snabbt kokar ihop i en tråd som denna görs givetvis inte, den koden hade inte ens nått 10 000 men bara att förstå hur viktigt det är att ha hanterbar kod, det är något som förbryllat mig varför inte fler förstår

Spenderat timtal i diskussioner med andra utvecklare som ser ut som ufon så fort man pratar generella lösningar

Permalänk
Datavetare
Skrivet av klk:

Hur menar du att din lösning inte var "hemma-kokad" ?

Ta följande sekvens

for (std::size_t i = 0; i < Rows; ++i) { std::cout << '|'; for (std::size_t j = 0; j < TotalCols; ++j) { std::cout << std::setw(5) << static_cast<int>(tbl[i][j]) << " |"; } std::cout << '\n'; }

Förstår att du ville göra något liknande, inte skriva en färdig lösning. Så helt ok, denna hade ju direkt hamnat i sopsäcken för normala lösningar.

Men vad jag inte förstår är att man inte ser vinsten i att kunna återanvända.

Klistrar in en bild här för att visa andra format som kan skriva ut tabellen. Aktuellt fall handlar om hjälptext för terminal applikation. Om text måste få plats inom en viss bredd behöver logik skrivas för att bryta texten. Räkna ut hur många rader texten behöver. Inte nog med det utan de andra kolumnerna behöver också förlängas med samma antal rader.
Det här tar en del tid att skriva kod för och den tiden hade inte investerats i en lösning som inte är återanvändbar, då exploderar koden i storlek och går inte hantera.
Kan man däremot skriva återanvändbar logik är investerad tid mycket bättre använd för plötsligt har man en metod som kan användas lite överallt.

<Uppladdad bildlänk>

Programmerare som vet att skriva återanvändbar kod skriver säkrare kod och är mycket snabbare än de som hårdkodar

Och då jobbar du också i en inarbetat arkitektur, antingen har det funnits utvecklare som vet hur designar kod eller så har de använt något färdigt. Att nå +50 000 rader kod med att skriva likt exempelkod som man kanske snabbt kokar ihop i en tråd som denna görs givetvis inte, den koden hade inte ens nått 10 000 men bara att förstå hur viktigt det är att ha hanterbar kod, det är något som förbryllat mig varför inte fler förstår

Spenderat timtal i diskussioner med andra utvecklare som ser ut som ufon så fort man pratar generella lösningar

Hmm, att rita ut en tabell är där enbart för att exemplet ska ge samma output som du postade ovan. Just den delen är 100 % ChatGPT-skriven. Den relevanta delen för diskussionen var allt innan det och rimligen är det inte funktionen att kunna skriva ut en ASCII-tabell som är "killer-feature" för din tabel.

Så vad är "killer-feature" som inte är trivialt att göra med standard collections och algoritmer?

Allt du har i din svart/blåa ruta är också funktioner som finns i standardbiblioteket sedan i alla fall C++11.

Edit: att parse command-line-argument är i.o.f.s. något man inte direkt får från standardbiblioteket, men finns en rad färdiga bibliotek som hanterar just det. T.ex. https://github.com/p-ranav/argparse

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:

Kortis (sent)
Såg du att tabellen vart utskriven med kolumner som beräknat bredden?
Hur tror du tabellen gör det om den inte har koll på varenda värde (angående tagged unions).

Jag kan bara gissa saker angående din gd::table eftersom du inte har visat koden. Men i det här fallet räcker det med typ och dimensionerna din matris har (8×10).

Hur sparar du den informationen?

Tittar man på tidigare exempel som:

gd::table::dto::table tablePrint(0u, { {"int32", 0, "index"}, {"rstring", 0, "name"}, {"rstring", 0, "line"} }, gd::table::tag_prepare{});

Här antar jag att du sparar en "tag" per kolumn?

Innebär det att den här versionen med 100000 kolumner sparar 100000 "taggar" med information om varje kolumn?

table tableSimd( uRowCount, 0, 0, gd::types::type_g( "double" ), 100'000, gd::table::tag_prepare{} );

Det känns som väldigt mycket krångel. Att oavsett vad du vill göra så är lösningen gd::table. Jag skulle skriva samma sak så här:

m,+/m←(10×⍳10)∘.+⍳8 0 1 2 3 4 5 6 7 28 10 11 12 13 14 15 16 17 108 20 21 22 23 24 25 26 27 188 30 31 32 33 34 35 36 37 268 40 41 42 43 44 45 46 47 348 50 51 52 53 54 55 56 57 428 60 61 62 63 64 65 66 67 508 70 71 72 73 74 75 76 77 588 80 81 82 83 84 85 86 87 668 90 91 92 93 94 95 96 97 748

Permalänk
Medlem
Skrivet av Yoshman:

Så vad är "killer-feature" som inte är trivialt att göra med standard collections och algoritmer?

Det är flera men för att fokusera på utskrifter finns följande, saknas någon kan jag snabbt lägga till den

/// tag dispatcher used for csv formatting struct tag_io_csv {}; /// tag dispatcher used for json formatting struct tag_io_json {}; /// tag dispatcher used for xml formatting struct tag_io_xml {}; /// tag dispatcher used for command line interface struct tag_io_cli {}; /// tag dispatcher used for raw formatting, this is used to write data without any formating, just as it is in table struct tag_io_raw {}; /// tag dispatcher for uri formatting struct tag_io_uri {}; /// tag dispatcher for sql formatting struct tag_io_sql {};

Observera namngivningen också för namngivning tror jag är något som slarvas mest med.
En C++ utvecklare kan förmodligen en del metodnamn vanliga i STL, förstår vad "to_string" är för något eftersom det också finns sådana metoder i STL biblioteket. Väljs namn och att kod skrivs med samma principer som STL kommer utvecklare ha lättare att jobba i koden. Det här är kanske en av de "smartaste" detaljerna i stl även om det finns några missar i namngivning. Vet man bara några få ord går det mesta att använda i stl.
Namn som begin, end, size, empty, clear, to_string, insert, find o.s.v

Läses kod och man ser att de håller sig till de här namnen är koden normalt enklare att förstå. När metodnamn skenar i väg i antal ord blir det så mycket svårare och det är en stark signal om att metoder gör flera saker,

Permalänk
Medlem
Skrivet av jclr:

Innebär det att den här versionen med 100000 kolumner sparar 100000 "taggar" med information om varje kolumn?

Om tabellen har 10 kolumner så spara information för varje kolumn, värdena i tabellen behöver inte någon information så det är i princip ett enda block med rader x storlek för data. så om det är 10 double så är det rader * 10 * sizeof(double)

1000 rader blir då 1000 * 10 * 8 = 80 000 byte.

Permalänk
Medlem
Skrivet av Yoshman:

Edit: att parse command-line-argument är i.o.f.s. något man inte direkt får från standardbiblioteket, men finns en rad färdiga bibliotek som hanterar just det. T.ex. https://github.com/p-ranav/argparse

Ja och det är väl den enda? märkligt att något så enkelt har så få bibliotek att välja på. Har som du säkert sett skrivit en egen med mer flexibilitet än argparse

Permalänk
Datavetare
Skrivet av klk:

Ja och det är väl den enda? märkligt att något så enkelt har så få bibliotek att välja på. Har som du säkert sett skrivit en egen med mer flexibilitet än argparse

Nu har jag skrivit egna varianter, fast aldrig i C++ utan i C, då det finns rätt många bra redan. Detta är ändå något som borde finnas i standardbiblioteket, i alla fall en enklare form som Pythons argsparse och Go flag.

Tog C++ argparse för det är vad jag använt sist. Det har rätt många features, nog för att implementera t.ex. git:s "git CMD SUBCOMMAND OPTIONAL_FLAGS <ARGS>" varianter.

Men finns flera andra, t.ex. CLI11, cxxopts (rätt mycket C++ versionen av Python argparse), Lyra, Boost.Program_options. Finns fler, men alla de listade såg jag alla under aktiv support/utveckling.

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:

Nu har jag skrivit egna varianter, fast aldrig i C++ utan i C, då det finns rätt många bra redan. Detta är ändå något som borde finnas i standardbiblioteket, i alla fall en enklare form som Pythons argsparse och Go flag.

Tog C++ argparse för det är vad jag använt sist. Det har rätt många features, nog för att implementera t.ex. git:s "git CMD SUBCOMMAND OPTIONAL_FLAGS <ARGS>" varianter.

Men finns flera andra, t.ex. CLI11, cxxopts (rätt mycket C++ versionen av Python argparse), Lyra, Boost.Program_options. Finns fler, men alla de listade såg jag alla under aktiv support/utveckling.

Tittade på det här för drygt 2 år sedan och vad som jag framförallt saknade var logik för att skicka in olika typer av format. De var centrerade kring att skicka med det som tas emot i main. Att parsa en sträng likt den som man skriver i terminalen direkt eller att generera en sträng (andra hållet) saknades. Förutom det så tror jag det var krångligt att lägga till argument efter parsning eller modifiera lästa argument

Vad som däremot fanns mycket av är validering och regler kring argument, även kontroller av typ.

/// Parse application arguments (like they are sent to `main`) std::pair<bool, std::string> parse( int iArgumentCount, const char* const* ppbszArgumentValue, const options* poptionsRoot ); std::pair<bool, std::string> parse( int iArgumentCount, const char* const* ppbszArgumentValue ) { return parse( iArgumentCount, ppbszArgumentValue, nullptr ); } /// parse single string, splits string into parts and parse as normal std::pair<bool, std::string> parse( const std::string_view& stringArgument ); /// parse vector of strings as normal std::pair<bool, std::string> parse( const std::vector<std::string>& vectorArgument ); /// parse single terminal line, splits string into parts and parse as normal, this is the exakt format as you type in terminal std::pair<bool, std::string> parse_terminal( const std::string_view& stringArgument ); std::string to_string() const;

Dold text
Permalänk
Datavetare
Skrivet av klk:

Det är flera men för att fokusera på utskrifter finns följande, saknas någon kan jag snabbt lägga till den

/// tag dispatcher used for csv formatting struct tag_io_csv {}; /// tag dispatcher used for json formatting struct tag_io_json {}; /// tag dispatcher used for xml formatting struct tag_io_xml {}; /// tag dispatcher used for command line interface struct tag_io_cli {}; /// tag dispatcher used for raw formatting, this is used to write data without any formating, just as it is in table struct tag_io_raw {}; /// tag dispatcher for uri formatting struct tag_io_uri {}; /// tag dispatcher for sql formatting struct tag_io_sql {};

Observera namngivningen också för namngivning tror jag är något som slarvas mest med.
En C++ utvecklare kan förmodligen en del metodnamn vanliga i STL, förstår vad "to_string" är för något eftersom det också finns sådana metoder i STL biblioteket. Väljs namn och att kod skrivs med samma principer som STL kommer utvecklare ha lättare att jobba i koden. Det här är kanske en av de "smartaste" detaljerna i stl även om det finns några missar i namngivning. Vet man bara några få ord går det mesta att använda i stl.
Namn som begin, end, size, empty, clear, to_string, insert, find o.s.v

Läses kod och man ser att de håller sig till de här namnen är koden normalt enklare att förstå. När metodnamn skenar i väg i antal ord blir det så mycket svårare och det är en stark signal om att metoder gör flera saker,

Fast det verkar väl inte helt optimalt, eller? Hur är det med "single responsibility principle" där? Varför ska en tabell, något du beskrivit som en "smart/flexibel container", veta hur den ska presentera sig själv?

Borde inte hur man lagrar/traverserar/accessar data vara rätt skilt från hur man i ett visst läge vill presentera data? Är ju inte superkomplext och ta en rätt godtycklig DAG (Directed Acyclic Graph), eventuellt med både value (all data serialiseras in-place) och referens (by-value varianten finns _någonstans_ i grafen men man vill här bara skriva ned vilken instans man pekar på), detta så pekare/referenser blir rätt i RAM efter inläsning. (Var det jag jobbade med här om veckan där YAML är serialiseringsformat, VAD man sparar är högst godtyckligt och går att stoppa in de flesta container-typer och objekt-typer).

Och vad skulle tabellen i ditt exempel göra om man väljer att tagga den som t.ex. URI (tag_io_uri) eller CLI (tag_io_cli), det är trots allt bara en trave flyttal i den just nu.

Eller ÄR just presentationen "killer-feature" för din tabell? Medan det rätt mycket bara är en form av std::vector<>/std::array<> m.a.p. egenskaper som container?

Kort och gott: vad är din hiss-pitch för denna smarta/flexibla funktion, d.v.s. förklaringen du ger till din chef/stakeholder som betalat för att du spenderat tid på att utveckla det?

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

Tittade på det här för drygt 2 år sedan och vad som jag framförallt saknade var logik för att skicka in olika typer av format. De var centrerade kring att skicka med det som tas emot i main. Att parsa en sträng likt den som man skriver i terminalen direkt eller att generera en sträng (andra hållet) saknades. Förutom det så tror jag det var krångligt att lägga till argument efter parsning eller modifiera lästa argument

Vad som däremot fanns mycket av är validering och regler kring argument, även kontroller av typ.

/// Parse application arguments (like they are sent to `main`) std::pair<bool, std::string> parse( int iArgumentCount, const char* const* ppbszArgumentValue, const options* poptionsRoot ); std::pair<bool, std::string> parse( int iArgumentCount, const char* const* ppbszArgumentValue ) { return parse( iArgumentCount, ppbszArgumentValue, nullptr ); } /// parse single string, splits string into parts and parse as normal std::pair<bool, std::string> parse( const std::string_view& stringArgument ); /// parse vector of strings as normal std::pair<bool, std::string> parse( const std::vector<std::string>& vectorArgument ); /// parse single terminal line, splits string into parts and parse as normal, this is the exakt format as you type in terminal std::pair<bool, std::string> parse_terminal( const std::string_view& stringArgument ); std::string to_string() const;

Dold text

Ah, är du ute efter att en command-line parser, d.v.s. argument skickade via argc/argv eller är du ute efter Cisco/Juniper CLI style interface (är det jag kan komma på som lite fler faktiskt använt, har gjort motsvarande i andra embedded program, Python har liknande stöd i sitt "cmd" bibliotek) där man skriver commandon, vill ha historik, tab-completion etc?

Finns ju väldigt mycket färdiga legoklossar för C++ i det senare fallet också, så man själv kan fokusera på sin specifika domän.

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:

Fast det verkar väl inte helt optimalt, eller? Hur är det med "single responsibility principle" där? Varför ska en tabell, något du beskrivit som en "smart/flexibel container", veta hur den ska presentera sig själv?

Det är free functions (separata metoder) likt stls to_string som tar tabell som parameter. För precis som du säger, absolut förbjudet att blanda logik i generella objekt.

Skrivet av Yoshman:

Borde inte hur man lagrar/traverserar/accessar data vara rätt skilt från hur man i ett visst läge vill presentera data?

Det är viktigt

Skrivet av Yoshman:

Eller ÄR just presentationen "killer-feature" för din tabell? Medan det rätt mycket bara är en form av std::vector<>/std::array<> m.a.p. egenskaper som container?

Enligt mig så är det viktigt att lösa problem på enklast möjliga sätt, ofta går det att få ihop något som fungerar flexibelt utan att det är så märkvärdigt. Tabellen var ursprungligen framtagen för att ersätta "dto" objekt (data transfer object). Få bort en hel massa structer som används för att fånga upp data från databas frågor (SELECT) och förenkla arbetet. För det vart mycket buggar med så många olika samt värden att hålla reda på. Få in allt i en enda tabell och bygga logiken kring den istället förenklade lösningen och det var enklare och verifiera (matcha mot databas och liknande). Sedan har det växt

Skrivet av Yoshman:

Kort och gott: vad är din hiss-pitch för denna smarta/flexibla funktion, d.v.s. förklaringen du ger till din chef/stakeholder som betalat för att du spenderat tid på att utveckla det?

Detta är open source så tror ingen är beredd att betala. Och det är relativt färdigt. Men det beror på vad som önskas göra, mest hjälp tror jag databasapplikationer som hanterar mycket SELECT frågor har. De skulle kunna rensa bort mycket kod om de idag använder dto'er för att samla data.

Permalänk
Medlem
Skrivet av Yoshman:

Ah, är du ute efter att en command-line parser, d.v.s. argument skickade via argc/argv eller är du ute efter Cisco/Juniper CLI style interface (är det jag kan komma på som lite fler faktiskt använt, har gjort motsvarande i andra embedded program, Python har liknande stöd i sitt "cmd" bibliotek) där man skriver commandon, vill ha historik, tab-completion etc?

Ja precis och även att testaköra applikationer. Scripta dem så att man exempelvis kan ha en fil med en hel hög olika argumentsekvenser som testkör olika saker.
"egen" history har också varit viktigt

Skrivet av Yoshman:

Finns ju väldigt mycket färdiga legoklossar för C++ i det senare fallet också, så man själv kan fokusera på sin specifika domän.

Vet att jag tyckte det var extremt märkligt att det var så svårt att hitta saker men kanske letade jag på fel ställen. Det var andra som också letade och det vi började med var argparse men den var verkligen ingen hitt. Det kanske finns bättre saker nu

Permalänk
Medlem
Skrivet av klk:

Om tabellen har 10 kolumner så spara information för varje kolumn, värdena i tabellen behöver inte någon information så det är i princip ett enda block med rader x storlek för data. så om det är 10 double så är det rader * 10 * sizeof(double)

1000 rader blir då 1000 * 10 * 8 = 80 000 byte.

Det var inte riktigt svar på min fråga. Om jag uppfattar dig rätt så om man har 100000 kolumner så kommer din gd::table spara information för varje kolumn angående t.ex. typ? Hur stor i bytes är den information per kolumn?

Permalänk
Medlem
Skrivet av jclr:

Det var inte riktigt svar på min fråga. Om jag uppfattar dig rätt så om man har 100000 kolumner så kommer din gd::table spara information för varje kolumn angående t.ex. typ? Hur stor i bytes är den information per kolumn?

Aha, tror jag förstår frågan
Det stämmer att tabellen inte är en bra lösning om alla värden ligger på rad. Men där finns den "normala" std::vector. Den typen av bearbetning har stl bra lösning för

Permalänk
Medlem
Skrivet av klk:

Aha, tror jag förstår frågan
Det stämmer att tabellen inte är en bra lösning om alla värden ligger på rad. Men där finns den "normala" std::vector. Den typen av bearbetning har stl bra lösning för

Det är det här jag var ute efter när jag skrev att din datalayout inte var SIMD-vänlig. Om det spelar roll eller inte har naturligtvis att göra med typ av/hur många beräkningar och hur mycket data du har.

Som du själv har märkt med tanke på ditt SIMD exempel tidigare så måste du ha all data på rad i minnet vilket innebär att om du använder din gd::table med mycket data så hamnar du i situationen med få rader men många kolumner vilket gd::table inte är anpassad för.

Om du istället faktiskt tillämpade DOD för datalayout internt och använde en layout där varje kolumns data sparas direkt efter varandra på rad i minnet så skulle du i många situationer få bättre utnyttjande av cache och möjlighet att använda SIMD.

Säg att du har något som liknar det här som innehåller flera kolumner och ett antal miljoner rader:

table data(… { {… "symbolId"}, {…} , {… "timestamp"}, {… "orderId}, {"int32", 0, "volume"}, {…}, {"int32", 0, "price"}, {…}}, …)

Du vill summera totala värdet på alla poster där volume×price överstiger ett visst värde. Jämför med din nuvarande datalayout vad som kommer hamna i cache och hur du skulle kunna använda SIMD för att göra beräkningen.

Permalänk
Medlem
Skrivet av jclr:

Det är det här jag var ute efter när jag skrev att din datalayout inte var SIMD-vänlig. Om det spelar roll eller inte har naturligtvis att göra med typ av/hur många beräkningar och hur mycket data du har.

Är med, man kan visserligen göra en tabell med en enda kolumn men ser inte riktigt poängen i det för som du beskriver så ligger värden på rad och samma operation skall göra på varje värde så är det enklare med std::vector.

Skrivet av jclr:

Som du själv har märkt med tanke på ditt SIMD exempel tidigare så måste du ha all data på rad i minnet vilket innebär att om du använder din gd::table med mycket data så hamnar du i situationen med få rader men många kolumner vilket gd::table inte är anpassad för.

Men där måste du oavsett ordna om data. Om det görs i en lokal buffer (kanske på stacken), en vector eller om man har något egen snickrat som jag. Det måste ändå utföras ett arbete innan för att få till det hela.

Skrivet av jclr:

Om du istället faktiskt tillämpade DOD för datalayout internt och använde en layout där varje kolumns data sparas direkt efter varandra på rad i minnet så skulle du i många situationer få bättre utnyttjande av cache och möjlighet att använda SIMD.

Men var det inte det jag gjorde?
Gissar jag rätt att du tittat en del på hur spelutvecklare applicerar DOD? Spel (grafik) brukar inte ha så många olika beräkningssteg vad jag sett när de håller på. De har det lättare att lyckas placera data i långa rader och behålla det så.

Antal steg i olika beräkningar jag skrivit kod för kan vara över 20 och det är ofta en del komplexitet kring varje beräkning med flera olika värden som är involverade

Skrivet av jclr:

Säg att du har något som liknar det här som innehåller flera kolumner och ett antal miljoner rader:

table data(… { {… "symbolId"}, {…} , {… "timestamp"}, {… "orderId}, {"int32", 0, "volume"}, {…}, {"int32", 0, "price"}, {…}}, …)

Du vill summera totala värdet på alla poster där volume×price överstiger ett visst värde. Jämför med din nuvarande datalayout vad som kommer hamna i cache och hur du skulle kunna använda SIMD för att göra beräkningen.

Den egen snickrade lösningen kan plocka ut alla värden från en kolumn och lägga dem i en vector. det hade varit ett alternativ men jag hade nog istället valt att tråda lösningen.

/// @name harvest values from table into other type of container objects /// 0TAG0harvest.table_column_buffer ///@{ template <typename TYPE> std::vector<TYPE> harvest( uint64_t uRow, unsigned uColumn, unsigned uCount, tag_row ) const noexcept; template <typename TYPE> std::vector<TYPE> harvest( uint64_t uRow, const std::string_view& stringName, unsigned uCount, tag_row ) const noexcept { return harvest<TYPE>( uRow, column_get_index( stringName ), uCount, tag_row{} ); } template<typename TYPE> std::vector< TYPE > harvest( unsigned uColumn, uint64_t uFrom, uint64_t uCount ) const; template<typename TYPE> std::vector< TYPE > harvest( unsigned uColumn, uint64_t uFrom, uint64_t uCount, tag_null ) const; template<typename TYPE> std::vector< TYPE > harvest( const std::string_view& stringColumnName, uint64_t uFrom, uint64_t uCount ) const { return harvest<TYPE>( column_get_index(stringColumnName), uFrom, uCount); } template<typename TYPE> std::vector< TYPE > harvest( unsigned uColumn ) const { return harvest<TYPE>( uColumn, (uint64_t)0, get_row_count() ); } template<typename TYPE> std::vector< TYPE > harvest( const std::string_view& stringColumnName ) const { return harvest<TYPE>( column_get_index(stringColumnName), ( uint64_t )0, get_row_count()); } template<typename TYPE> std::vector< TYPE > harvest( const std::string_view& stringColumnName, tag_null ) const { return harvest<TYPE>( column_get_index(stringColumnName), ( uint64_t )0, get_row_count(), tag_null{}); } /// harvest row values into vector with arguments void harvest( uint64_t uBeginRow, uint64_t uCount, std::vector<gd::argument::arguments>& vectorArguments ) const; void harvest( std::vector<gd::argument::arguments>& vectorArguments ) const { harvest( 0, get_row_count(), vectorArguments ); } std::vector<gd::argument::arguments> harvest( tag_arguments ) const { std::vector<gd::argument::arguments> v_; harvest( 0, get_row_count(), v_ ); return v_; } void harvest( const std::vector<uint64_t>& vectorRow, std::vector<gd::argument::arguments>& vectorArguments ) const; void harvest( const std::vector<uint64_t>& vectorRow, std::vector< std::vector<gd::variant_view> >& vectorRowValue ) const; void harvest( const std::vector< unsigned >& vectorColumn, const std::vector<uint64_t>& vectorRow, table_column_buffer& tableHarvest ) const; void harvest( const std::vector< std::string_view >& vectorColumnName, const std::vector<uint64_t>& vectorRow, table_column_buffer& tableHarvest ) const; ///@}

Dold text
Permalänk
Medlem
Skrivet av klk:

Men var det inte det jag gjorde?
Gissar jag rätt att du tittat en del på hur spelutvecklare applicerar DOD? Spel (grafik) brukar inte ha så många olika beräkningssteg vad jag sett när de håller på. De har det lättare att lyckas placera data i långa rader och behålla det så.

Antal steg i olika beräkningar jag skrivit kod för kan vara över 20 och det är ofta en del komplexitet kring varje beräkning med flera olika värden som är involverade

Inte alls tittat på spelutveckling. Att skriva en egen spelmotor finns med en bit ner på den evigt växade listan av saker som skulle vara instressanta att sätta sig in i.

DOD används inom andra områden också. Det handlar egentligen bara om anpassa sin datalayout utifrån accessmönster istället för hur vi människor grupperar ihop datan i objekt.

Nu utgår jag ifrån hur jag brukar använda liknande strukturer som din table. Det vanligaste accessmönstret för mig är att göra beräkningar över samtliga eller ett urval av värdena som tillhör en eller flera kolumner men inte alla kolumner.

Att använda en annan intern datalayout handlade inte om ditt SIMD exempel, utan om datalayouten för t.ex. table(… {{"int32", 0, "col1"}, {"float", 0, "col2"}, …}:

Du använder:
int32 | float | int32 | float | int32 | float …

Effektivare layout för det jag oftast använder tables till:
int32 | int32 | int32 … | float | float | float …

Om du nu bara vill summera alla värden i "col1" så kommer processorn med din layout prefetcha hela cachelines där hälften av datan innehåller floats från "col2". Du fyller alltså halva cachen med information du inte behöver.

Nu handlar det helt om vilka accessmönster man faktiskt har. Om det är så att du i princip alltid använder alla kolumner från varje rad för alla beräkningar så spelar inte layouten någon roll för effektivt utnyttjande av cache. T.ex. vid spelprogrammering där du itererar över alla objekt så kommer inte x | y | z | x | y | z … jämfört med x | x … | y | y … | z | z … göra någon större skillnad när det gäller cache. Däremot är det betydligt enklare att utnyttja SIMD effektivt med "min" layout.

Den layout du har valt kan vara helt rätt för dina användningsområden men det visar nackdelarna med att försöka skapa en generell återanvändningsbar lösning för allt. För mig är det "fel" datalayout för 99% av mina användningsområden.

Skrivet av klk:

Den egen snickrade lösningen kan plocka ut alla värden från en kolumn och lägga dem i en vector. det hade varit ett alternativ men jag hade nog istället valt att tråda lösningen.

/// @name harvest values from table into other type of container objects /// 0TAG0harvest.table_column_buffer ///@{ template <typename TYPE> std::vector<TYPE> harvest( uint64_t uRow, unsigned uColumn, unsigned uCount, tag_row ) const noexcept; template <typename TYPE> std::vector<TYPE> harvest( uint64_t uRow, const std::string_view& stringName, unsigned uCount, tag_row ) const noexcept { return harvest<TYPE>( uRow, column_get_index( stringName ), uCount, tag_row{} ); } template<typename TYPE> std::vector< TYPE > harvest( unsigned uColumn, uint64_t uFrom, uint64_t uCount ) const; template<typename TYPE> std::vector< TYPE > harvest( unsigned uColumn, uint64_t uFrom, uint64_t uCount, tag_null ) const; template<typename TYPE> std::vector< TYPE > harvest( const std::string_view& stringColumnName, uint64_t uFrom, uint64_t uCount ) const { return harvest<TYPE>( column_get_index(stringColumnName), uFrom, uCount); } template<typename TYPE> std::vector< TYPE > harvest( unsigned uColumn ) const { return harvest<TYPE>( uColumn, (uint64_t)0, get_row_count() ); } template<typename TYPE> std::vector< TYPE > harvest( const std::string_view& stringColumnName ) const { return harvest<TYPE>( column_get_index(stringColumnName), ( uint64_t )0, get_row_count()); } template<typename TYPE> std::vector< TYPE > harvest( const std::string_view& stringColumnName, tag_null ) const { return harvest<TYPE>( column_get_index(stringColumnName), ( uint64_t )0, get_row_count(), tag_null{}); } /// harvest row values into vector with arguments void harvest( uint64_t uBeginRow, uint64_t uCount, std::vector<gd::argument::arguments>& vectorArguments ) const; void harvest( std::vector<gd::argument::arguments>& vectorArguments ) const { harvest( 0, get_row_count(), vectorArguments ); } std::vector<gd::argument::arguments> harvest( tag_arguments ) const { std::vector<gd::argument::arguments> v_; harvest( 0, get_row_count(), v_ ); return v_; } void harvest( const std::vector<uint64_t>& vectorRow, std::vector<gd::argument::arguments>& vectorArguments ) const; void harvest( const std::vector<uint64_t>& vectorRow, std::vector< std::vector<gd::variant_view> >& vectorRowValue ) const; void harvest( const std::vector< unsigned >& vectorColumn, const std::vector<uint64_t>& vectorRow, table_column_buffer& tableHarvest ) const; void harvest( const std::vector< std::string_view >& vectorColumnName, const std::vector<uint64_t>& vectorRow, table_column_buffer& tableHarvest ) const; ///@}

Dold text

Att använda harvest på det där sättet fungerar utmärkt om man har många upprepade beräkningar på samma urval av data. Enkelt att dela upp en table över flera trådar dessutom där varje tråd kan arbeta med data som är i ett cache/simd -vänligt format. Det är ett bra exempel på hur man kan omvandla data till ett DOD format för själva beräkningarna.

stavfel