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

Permalänk
Medlem
Skrivet av huttala:

Nu har ja svårt att förstå varför dina enums är DOD. Så då kan du väl svara på min fråga istället för att samla om annat? Du kan ju plocka ut tre element från koden du skrev och göra ett use case på 2 minuter?

// 1: återanvänd tabell (flexibel tabell för att hantera tabelldata) detail::columns* pcolumnsThread = new detail::columns{}; ptableLineList->to_columns( *pcolumnsThread ); std::unique_ptr<table> ptableLineListLocal = std::make_unique<table>(pcolumnsThread, 10, ptableLineList->get_flags(), 10); // 2 argument objekt gd::argument::shared::arguments arguments_({{"source", stringFile}, {"file-key", uKey}}); if( stringSegment.empty() == false ) arguments_.set("segment", stringSegment.data()); // Set the segment (code, comment, string) to search in auto result_ = COMMAND_ListLinesWithPattern(arguments_, vectorRegexPatterns, ptableLineListLocal.get()); // Find lines with regex patterns and update the local table // 3 Namngivna argument MESSAGE_Progress("", {{"percent", uPercent}, {"label", "Find in files"}, {"sticky", true}});

Permalänk
Medlem
Skrivet av klk:

...
Det svåra idag är annars det jag exempelvis åker på i den här tråden, det är så många som VET hur man egentligen skall göra.
...

I klartext: om man läser alla inlägg härifrån och ett antal sidor bakåt så är det i princip bara Du som stämmer in på "VET hur man egentligen ska göra"-beskrivningen. Vi behöver inte lägga någon värdering i det. Så gott som alla andra ställer öppna frågor och motfrågor i hopp om att försöka förstå VAD det är du försöker säga.

Flera av oss är universitet-/högskoleutbildade ingenjörer och är sedan länge inpräntade med att påståenden behöver backas upp för att bära någon substans.

Vi har sett dina påståenden, det vi efterfrågar är substansen. Gör det saken tydligare?

Skrivet av klk:

... Du kan inte vara så känslomässig som du är [riktat till orp], programmering är för ingenjörer

Med samma logik borde det vara tydligt att fakta är vad som efterfrågas, faktan som underbygger dina åsikter (för åsikter som inte bygger på fakta skulle jag vilja klassa in i kategorin "känsla").

Visa signatur

Citera mig för svar.
Arch Linux

Permalänk
Medlem
Skrivet av klk:

// 1: återanvänd tabell (flexibel tabell för att hantera tabelldata) detail::columns* pcolumnsThread = new detail::columns{}; ptableLineList->to_columns( *pcolumnsThread ); std::unique_ptr<table> ptableLineListLocal = std::make_unique<table>(pcolumnsThread, 10, ptableLineList->get_flags(), 10); // 2 argument objekt gd::argument::shared::arguments arguments_({{"source", stringFile}, {"file-key", uKey}}); if( stringSegment.empty() == false ) arguments_.set("segment", stringSegment.data()); // Set the segment (code, comment, string) to search in auto result_ = COMMAND_ListLinesWithPattern(arguments_, vectorRegexPatterns, ptableLineListLocal.get()); // Find lines with regex patterns and update the local table // 3 Namngivna argument MESSAGE_Progress("", {{"percent", uPercent}, {"label", "Find in files"}, {"sticky", true}});

Kan du nu förklara hur detta är relaterat till DOD?

Permalänk
Medlem
Skrivet av orp:

Kan du nu förklara hur detta är relaterat till DOD?

För mig är DOD runtime data anpassad för att fungera så bra som möjligt för en processor.

Permalänk
Medlem
Skrivet av klk:

För mig är DOD runtime data anpassad för att fungera så bra som möjligt för en processor.

Ett faktiskt svar. Om jag förstått DOD rätt så handlar det om att fokusera på att data är cache line-optimerad så vi är någorlunda i linje med det svaret.

Så om vi utgår från vår nyfunna middle-ground för definitionen av DOD och tar en titt på dina andra påstående ihop med DOD:

Skrivet av klk:

Du kan skriva återanvändbar kod

Varför skulle DOD leda till mer återanvändbar kod om man stirrar sig blind på cache line-optimering istället för användarvänlighet och flexibilitet?

Skrivet av klk:

// 1: återanvänd tabell (flexibel tabell för att hantera tabelldata) detail::columns* pcolumnsThread = new detail::columns{}; ptableLineList->to_columns( *pcolumnsThread ); std::unique_ptr<table> ptableLineListLocal = std::make_unique<table>(pcolumnsThread, 10, ptableLineList->get_flags(), 10); // 2 argument objekt gd::argument::shared::arguments arguments_({{"source", stringFile}, {"file-key", uKey}}); if( stringSegment.empty() == false ) arguments_.set("segment", stringSegment.data()); // Set the segment (code, comment, string) to search in auto result_ = COMMAND_ListLinesWithPattern(arguments_, vectorRegexPatterns, ptableLineListLocal.get()); // Find lines with regex patterns and update the local table // 3 Namngivna argument MESSAGE_Progress("", {{"percent", uPercent}, {"label", "Find in files"}, {"sticky", true}});

Vad i denna koden antyder på cache line-optimering?

Skrivet av klk:

Förstår du koden i den här posten #20969374

Om man optimerar för cache line och försöker spara cykler för att bevara FPS i spel eller annan prestanda. Går det inte emot DODs designfilosofi om man spenderar extra cykler på att annotera redundant typinfo?

Permalänk
Medlem
Skrivet av orp:

Ett faktiskt svar. Om jag förstått DOD rätt så handlar det om att fokusera på att data är cache line-optimerad så vi är någorlunda i linje med det svaret.

Så till dina andra påstående:

Varför skulle DOD leda till mer återanvändbar kod om man stirrar sig blind på cache line-optimering istället för användarvänlighet och flexibilitet?

Vad i denna koden antyder på cache line-optimering?

För att om man anpassar maskinkoden så den är snabb går det att göra mycket mer, extra prestanda kan användas för att öka funktionaliteten och få flexiblare kod.

Permalänk
Medlem
Skrivet av orp:

Om man optimerar för cache line och försöker spara cykler för att bevara FPS i spel eller annan prestanda. Går det inte emot DODs designfilosofi om man spenderar extra cykler på att annotera redundant typinfo?

Enligt mig så är inte DOD bara för snabbhet även om det mest är spelutvecklare som praktiserar det och i princip allt jag kodat anpassar sig efter multiplar av 4 eller 16 byte. Det plus extra säker kod eftersom all data har information om vad det är för data är värt mycket för att få fram saker.

Ta exmpelvis BOOST json,. deras json objekt är 48 byte medan nästan alla json bibliotek på github har 32. Att BOOST behöver mer tror jag beror på att BOOST måste anpassa sig för att fungera överallt, max antal måste vara ett 64 bitars tal på en 64 bitars dator medan andra kan "fuska" och välja 32 bitars tal som max antal även om de kör på 64 bitars dator.
BOOST är inte lika DOD som vad andra kan vara. STL lider av samma problem

I programkod brukar det bara vara några enstaka ställen där det räcker att optimera så påverkar det allt. och processorer har fått så mycket kärnor. Tror därför parallellisering är prioritet

Permalänk
Skrivet av klk:

Kan vi göra så här istället, om jag ber dig förklara hur du skriver återanvändbar kod så har jag något att relatera till (kanske hur jag skulle ha gjort), tror det blir enklare

OK, då kör vi.

Min vanligaste form av återanvändning är utan tvekan funktioner.

Nu gör jag andra saker men mitt senaste stora mjukvaruprojekt var en simulator av framtida produkter. Jag, som en del av ett team på 8 till 10 personer, utvecklade och underhöll centrala komponenter till denna simulator. Dessa komponenter kom i olika smaker för att simulera olika versioner av dessa framtida produkter. Det förekom även experimentvarianter för att utvärdera om olika typer av ny funktionalitet skulle vara användbar eller inte.

Själva simuleringsramverket är i C och använder en poor man's OO-modell med strukturer av funktionspekare. Detta är bara en objektmodell som används för simuleringen, inget arv, men man kan förstås återanvända funktionspekare för objekt som har likartad funktionalitet. Våra komponenter skrivs mestadels i C men vissa bitar är i C++. Mindre tidskritiska delar skrivs i Python (interpretatorn integrerad i simulatorn). Pythondelarna använder arv för att återanvända kod som är gemensam för snarlika komponenter. Återanvändning av kod består av simuleringsramverket i sig, bibliotek med supportfunktioner som används av olika varianter av våra komponenter, Pythonmoduler. Våra komponenter återanvänds när andra team bygger olika simulatorer för olika versioner av framtida produkter.

Tidigare jobbade (20+ år) med ett stort C++-projekt. Där var återigen funktioner den tveklöst vanligaste formen av återanvändning. Sedan är det frågan om vad som var på andra plats. Här användes vi arv och templates för återanvändning av kod, men frågan är om det inte var mer återanvändning när modulen jag jobbade med återanvändes i alla våra produkter (ca 20).

I båda fallen är slutresultatet ett program som körs direkt från kommandoraden eller startas av ett annat program.

Hur återanvänder du kod?

Permalänk
Datavetare
Skrivet av klk:

// 2 argument objekt gd::argument::shared::arguments arguments_({{"source", stringFile}, {"file-key", uKey}}); if( stringSegment.empty() == false ) arguments_.set("segment", stringSegment.data()); // Set the segment (code, comment, string) to search in auto result_ = COMMAND_ListLinesWithPattern(arguments_, vectorRegexPatterns, ptableLineListLocal.get()); // Find lines with regex patterns and update the local table

Fråga: vad vinner man på att använda gd::argument::... istället bara för att göra detta som rimligen de flesta som kan C++ borde ha betydligt lättare att läsa/förstå?

// 2 argument objekt std::unordered_map<std::string, std::string> arguments_{ {"source", stringFile}, {"file-key", uKey} }; if (!stringSegment.empty()) { arguments_["segment"] = stringSegment; } auto result_ = COMMAND_ListLinesWithPattern(arguments_, vectorRegexPatterns, ptableLineListLocal.get());

Är gd::argument med DOD på något sätt?

Skrivet av klk:

Enligt mig så är inte DOD bara för snabbhet även om det mest är spelutvecklare som praktiserar det och i princip allt jag kodat anpassar sig efter multiplar av 4 eller 16 byte. Det plus extra säker kod eftersom all data har information om vad det är för data är värt mycket för att få fram saker.

Ta exmpelvis BOOST json,. deras json objekt är 48 byte medan nästan alla json bibliotek på github har 32. Att BOOST behöver mer tror jag beror på att BOOST måste anpassa sig för att fungera överallt, max antal måste vara ett 64 bitars tal på en 64 bitars dator medan andra kan "fuska" och välja 32 bitars tal som max antal även om de kör på 64 bitars dator.
BOOST är inte lika DOD som vad andra kan vara. STL lider av samma problem

I programkod brukar det bara vara några enstaka ställen där det räcker att optimera så påverkar det allt. och processorer har fått så mycket kärnor. Tror därför parallellisering är prioritet

Ge ett exempel på vad det är för JSON-filer du behöver parse:a om det ens är relevant om det är 32 eller 48 bytes per nod?
Dels får man in >100k sådana noder i vissa moderna CPUers L2$, dels så lär den relativa skillnaden det utgör av den totala mängd data som man ändå behöver jobba med vara ett avrundningsfel. Eller?

Att spel kan få stor utväxling på DOD är ju p.g.a att man där spara all relevant data "på rad" och man sedan kan t.ex. applicera förändring av position baserad på senaste frame-tid multiplicerad med aktuell hastigheter. Något som med DOD ligger på ett "bekvämt" sätt för att processas med SIMD (vilket spelar roll om det är ett gäng tuselentals till tiotuseltasl object).

Men processa JSON... Då har man ju ändå en pekare till själva strängen och kostnaden för parsning lär vida överstiga kostanden för att läsa de noder 32/48 bytes noder med meta-data.

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:

För att om man anpassar maskinkoden så den är snabb går det att göra mycket mer, extra prestanda kan användas för att öka funktionaliteten och få flexiblare kod.

Så i vårt språk(svenska) betyder "återanvända" att "använda på nytt". Exempelvis väldigt generellt skriven kod har ju större sannolikhet att återanvändas eftersom användningsområdet är större än låt säga ett nischat område(som kan optimeras pga sin avgränsning).

Lite som du är inne på med JSON-bitarna från Boost den är nischad till att enbart hantera JSON även om JSON och YAML är kompatibla representationsmässigt. Boost hade ju kunnat återanvända lexer, ha olika parsers och spotta ut samma AST(givet samma input så klart). Boost drog troligen gränsen för att enbart optimera för JSON vilket gör koden mindre återanvändbar men snabbare.

För att sammanfatta:

Återanvändbar är inte samma sak som snabb. Jag har inte format en åsikt så stark att jag skulle påstå att dom är mutually exclusive men min magkänsla säger att den ena oftast kommer på bekostnad av den andra.

Permalänk
Medlem
Skrivet av klk:

Enligt mig så är inte DOD bara för snabbhet även om det mest är spelutvecklare som praktiserar det och i princip allt jag kodat anpassar sig efter multiplar av 4 eller 16 byte.

Jag tolkar det som att DOD framförallt är pga hastighet och att det därför fått sådant dragning inom spelindustrin. Sedan så har jag en känsla av att betydelsen av DOD har blivit uppblåst av somliga personer eftersom man låter smart när man pratar om lågnivåoptimeringar. Nu bor jag i ett spelmecka och snackar förhållandevis frekvent med spelutvecklare och med vetskapen om deras arbetssituation(alltid skitstressigt) så har jag svårt att tro att någon skulle ha tid att prioritera sådana optimeringar. Det viktiga är att pumpa ut spel och är du utvecklar du långsamt så ryker du eftersom det står 500 andra sökande på tur.

Personligen har jag aldrig behövt bry mig om särskilt mycket om DOD, visst vi har tumregeln som att sortera struct-medlemmarna i fallande storlek så är du "good to go" eftersom man antar att utvecklaren inte placerar skräp i structen. För min del har det alltid varit viktigare med design så att man tänker på vart man kan och bör parallelliserar, tänka på minnesanvändningen, tänka på storlek, tänka på risken av sönderskrivningar, säkerhet osv. Jag tänker naturligt på datautformning men inte notoriskt eftersom det inte spelar roll för min del. Det viktigaste är att koden gör det den ska i en rimlig hastighet och är lätt att underhålla och är lätt utöka vid behov.

Skrivet av klk:

I programkod brukar det bara vara några enstaka ställen där det räcker att optimera så påverkar det allt. och processorer har fått så mycket kärnor. Tror därför parallellisering är prioritet

Precis så varför stirra sig blind på DOD och göra det till din religion?

Permalänk
Medlem
Skrivet av klk:

Det plus extra säker kod eftersom all data har information om vad det är för data är värt mycket för att få fram saker.

Gällande extra typinfo så har jag fortfarande inte sett någon direkt poäng med det. Det enda jag kan tänka på är om man ska implementera variants( och när har man behov av variants?) eller när ett språk saknar templates, generics och macros som Go inledningsvis gjorde och man behövde använda reflections flitigt för att hantera typer någorlunda generiskt.

Permalänk
Datavetare
Skrivet av orp:

Gällande extra typinfo så har jag fortfarande inte sett någon direkt poäng med det. Det enda jag kan tänka på är om man ska implementera variants( och när har man behov av variants?) eller när ett språk saknar templates, generics och macros som Go inledningsvis gjorde och man behövde använda reflections flitigt för att hantera typer någorlunda generiskt.

I C++ finns en poäng om man stoppar in typer med icke-trivial dtor i en union. I det läget måste man ha någon form av tag/typinfo för att veta vilken dtor som ska köras när unionen går out-of-scope.

Men känner också: antalet gånger jag känt behov av "variant" är väldigt få. Har använt det ett par gånger i Rust, kommer inte ihåg exakt till vad längre. Finns fall där variant kan ha sina poänger, men känns som specialfall och inte normalfall.

Har i.o.f.s. använt C# DynamicInvoke på ett ställe i kodbasen jag sitter med nu, då kan man ha mottagare med konkret typ i signaturen istället för att de alltid ska behöva ta någon gemensam bastyp och down-casta. Lite åt samma håll, fast där behöver man typinfo på en funktion för att kunna matcha den med data vill skicka.

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:

aspergers

Den här tråden går ju i cirklar offtopic, så mitt (totalt onödiga) bidrag för ikväll blir att återposta(?) Andrew Kellys utmärkta video om Data Oriented Design (tillämpad i Zig-kompilatorn, med exempel i språket).

Det finns ju ett gäng sätt att implementera objektorientering/arv/polymorfism i C (eller i C++ om man insisterar på det), där de berömda tagged unions är ett.

Videon har bäring på de senaste sidorna i tråden mest för att Kelly i mångt och mycket totalslaktar sina egna tagged unions, till förmån för vad han kallar "encoding". Har man inte sett videon förut så ska man nog med stor behållning börja från början, annars börjar slakten vid ca 23:25 och fortsätter in i exemplen från kompilatorn.

(det finns en skjortvideo från i år också, den är också lite intressant om man inte ser världen i svartvitt)

Permalänk
Medlem
Skrivet av Yoshman:

I C++ finns en poäng om man stoppar in typer med icke-trivial dtor i en union. I det läget måste man ha någon form av tag/typinfo för att veta vilken dtor som ska köras när unionen går out-of-scope.

Men känner också: antalet gånger jag känt behov av "variant" är väldigt få. Har använt det ett par gånger i Rust, kommer inte ihåg exakt till vad längre. Finns fall där variant kan ha sina poänger, men känns som specialfall och inte normalfall.

Har i.o.f.s. använt C# DynamicInvoke på ett ställe i kodbasen jag sitter med nu, då kan man ha mottagare med konkret typ i signaturen istället för att de alltid ska behöva ta någon gemensam bastyp och down-casta. Lite åt samma håll, fast där behöver man typinfo på en funktion för att kunna matcha den med data vill skicka.

Enda gången jag använt det var för att implementera ett Simula-programmeringsverktyg på Mac för många år sedan, måste varit MacOS 5.1 eller möjligen 6.0, alltså långt innan övergången till MacOSX (nuvarande MacOS). Systemprogrammeringen skedde i Object Pascal så för att mappa en del datastruktrer som var inbyggda i OSet och slippa ständiga konverteringar så definierade vi upp ett mellanskikt i varianta records i Object Pascal som vi sedan läste in i klasser i Simula, fullständigt osäkert, inga kontroller, men det fungerade. Grundsystemet för att få Simula att köra på Mac hade gjorts av entusiasterna på Lunds Universitet, Boris Magnusson och Göran Fries, det jag implementerade var alltså ett verktyg för att effektivt kunna skriva Simulaprogram som hanterade alla faciliteter i Macen inklusive alla inbyggda datastrukturer så att man kunde framställa fullödiga Mac-program med hjälp av Simula. Jag kallade det för Macintosh Simula Workshop en anspelning på Apples egna verktyg, Macintosh Programmers Workshop. Det här måste ha varit runt 1987 eller 1988. Avsikten var att KTH-studenter skulle kunna programmera Simula även på högre kurser, de hade använt språket på grundkurserna i programmering och man ville kunna fortsätta efter andra året utan att behöva byta språk. Det gjorde inget större avtryck förutom att det användes på KTH som satsade mycket på Mac och att jag fick presentera systemet på en Simulakonferens i Pilzen i dåvarande Tjeckoslovakien. Senare gick man över till andra programspråk och systemet föll i glömska. Det som var trevligt med systemet var att, förutom "klistermodulerna" i Object Pascal som mappade alla datastrukturer till Simula var hela systemet skrivet i Simula.
... Lång historia för att berätta om mitt sammanträffande med taggade unioner. Det är enda gången jag använt det. Jag har inte hållit på med programmering där jag tyckt de tillfört något sedan dess och då såg jag det som en nödlösning för att slippa skriva krångliga konverteringsfunktioner. Jag såg det mest som fusk (men fusk som funkade).

Visa signatur

Debian, bara Debian, Debian överallt

Permalänk
Medlem
Skrivet av klk:

Bad AI göra en jämförelse mellan std::vector och rusts Vec. Rust behöver normalt nära dubbelt så många olika metodnamn jämfört med C++ på grund av att de saknar överlagring. Det kanske inte upplevs som så problematiskt om man inte är van vid att skriva generell kod eller skriva kod som skall återanvändas.

Men det är problem som ökar snabbt eftersom man hela tiden behöver hitta på nya namn, namn är svåra att hantera.

Ta en jämförelse, låt säga att du har 1000 skruvar till en bil, är det 1000 olika skruvar hade bilen blivit mycket dyr. Desto mer lika du kan få skruvarna desto lättare blir bilen att skruva samman.

Med överlagring är det enklare att ha samma namnuppsättning för olika typer av objekt och då slipper utvecklare läsa igenom koden. Bra namngivning i C++ och du behöver i princip inte läsa kod, du vet hur du skall använda det eftersom det följer mönster (fasta strukturer) och där är namn en viktig del.

Problemet med avsaknad av överlagring blir speciellt jobbigt i värdeklasser. I alla fall har jag inte lyckats få AI att skriva om C++ kod till något som verkar trevligt och jobba med. std::vector som här jämförs är långt ifrån det värsta exemplet, tvärtom

Med det sagt så är rust ett jättebra undervisningsspråk. För nya utvecklare är tydliga namn lättare att förstå jämfört med "smart" C++ kod

***

### Comparison of `std::vector` (C++) and `Vec` (Rust) Method Proliferation

A key difference between the C++ and Rust APIs is the number of method names. C++ leverages function overloading and context, while Rust uses distinct names to explicitly signal mutability, ownership, and error handling.

**Operation: Get an element**
* **C++** (Fewer names): `operator[]`, `at`, `front`, `back`
* **Rust** (More names, explicit): `[]` (via Index trait), `get`, `get_mut`, `first`, `first_mut`, `last`, `last_mut`

**Operation: Add an element**
* **C++:** `push_back`, `emplace_back`
* **Rust:** `push`, `insert`

**Operation: Remove an element**
* **C++:** `pop_back`, `erase`
* **Rust:** `pop`, `remove`, `swap_remove`

**Operation: Create an iterator**
* **C++** (Overloaded names): `begin`, `end` (with `const`, non-`const`, and reverse variants)
* **Rust** (Explicit names): `iter` (immutable borrow), `iter_mut` (mutable borrow), `into_iter` (take ownership)

**Operation: Change size**
* **C++** (Single method): `resize`
* **Rust** (Specific methods): `resize`, `resize_with`

**Operation: Remove elements by condition**
* **C++** (Algorithm + method): Use `std::remove_if` + `erase`
* **Rust** (Built-in methods): `retain`, `drain`, `drain_filter` (unstable)

**Operation: Append another collection**
* **C++** (Overloaded method): `insert` (with iterators)
* **Rust** (Specific methods): `append`, `extend`, `extend_from_slice`

**Operation: Handle errors on access**
* **C++** (Exception handling): `at` (throws an exception on error)
* **Rust** (Type-based handling): `get`, `get_mut` (return an `Option<&T>` type)

Det verkar iaf som vi menar samma sak med överlagring men det finnns andra sätt att implementera ad-hoc polymorfism som t.ex. typklasser eller traits i Rust. Att implementera överlagring i sig i en kompilator är inte svårt utan har med det @Ingetledigtnamn tar upp att göra, hur överlagring interagerar med andra saker som finns i språket. I Rusts fall så gissar jag att det till stor del handlar om att man har Hindler-Milner typinferens istället för lokal typinferens.

Ditt AI exempel tycker jag inte visar varför det skulle vara så viktigt med överlagring? Rust namnen följer fasta mönster och gör koden lättare att förstå. Även om Rust hade haft överlagring så hade de fortfarande varit vettigare med separata funktioner med olika namn.

Permalänk
Medlem
Skrivet av Yoshman:

Fråga: vad vinner man på att använda gd::argument::... istället bara för att göra detta som rimligen de flesta som kan C++ borde ha betydligt lättare att läsa/förstå?

Exempel kod som inte är möjlig eller blir trasslig utan typinfo och vad jag tror är ett bra exempel på hur det här kan hjälpa till för bättre flexibilitet och som något de flesta utvecklare känner igen. Använder då mitt hobbyprojekt om att söka i kod.

Det som sökts fram behöver visas för användaren annars är det ingen mening med att söka.

I princip allt som skrivs ut från applikationen nu görs från den här metoden "PrintMessage" inklistrad. Och det fungerar för en terminalapplikation. Men har jag lagt ner en del tid på koden så varför vara begränsad till en terminal? Hårdkodas det mot terminal blir det en teknisk skuld.

Vill jag wrappa applikationen i något UI eller göra en plugin till editor så behöver den annan funktionalitet och då är det viktigt att allt som har och göra med att producera användarinformation är flexibelt samtidigt VET JAG INTE vad som kommer behövas.

När jag inte vet behöver jag något flexibelt.
Koden nedan använder då arguments objektet och eftersom den vet om typen så har den hel hel massa logik för det (med kommentarer är arguments en bit över 5000 rader).

PrintMessage visar hur man ganska enkelt få till något som går att förändra eller lägga till utan att göra sönder annan kod. Och allt hänger på hur flexibel hanteringen är av data. I arguments finns det exempelvis en hel del as_* och med index operatorn letar den fram rätt värde och det går att konvertera till vad som önskas.

Låt säga att jag får ett edge case där det måste in mycket mer information för att lösa något specifikt UI, teoretiskt kan jag då lägga in den koden för det och det är i runtime, inte nya kompilerade varianter).

std::pair<bool, std::string> CApplication::PrintMessage(const std::string_view& stringMessage, const gd::argument::arguments& argumentsFormat) { std::unique_lock<std::shared_mutex> lock_( m_sharedmutex ); enumUIType eUIType = m_eUIType; // Get the UI type from the application instance if( argumentsFormat.exists("ui") == true ) { std::string stringUIType = argumentsFormat["ui"].as_string(); eUIType = GetUITypeFromString_s( stringUIType ); } switch(eUIType) { case eUITypeUnknown: case eUITypeConsole: { if( argumentsFormat.exists("color") == true ) { std::string stringColor = argumentsFormat["color"].as_string(); if( stringColor.empty() == false ) { stringColor = CONFIG_Get("color", stringColor).as_string(); } if( stringColor.empty() == false ) { stringColor = gd::console::rgb::print(stringColor, gd::types::tag_color{}); std::cout << stringColor; // Print the color code before the message } } std::cout << stringMessage << std::endl; } break; case eUITypeWeb: // Implement web output logic here // e.g., send message to web UI log break; case eUITypeWIMP: // Implement desktop UI message box or log break; case eUITypeVSCode: break; #ifdef _WIN32 case eUITypeVS: { auto result_ = VS::CVisualStudio::Print_s( stringMessage, VS::tag_vs_output{}); if( result_.first == false ) { std::string stringError = std::format("Failed to print to Visual Studio: {}", result_.second); std::cerr << stringError << "\n"; return result_; } } break; #endif // _WIN32 case eUITypeSublime: // Implement extension output logic here break; default: // Fallback to console std::cout << stringMessage << std::endl; break; } return {true, ""}; }

Dold text

Jämför jag med den här koden så den flexibel men den lagrar strängar och informationen om det vet bara kompilatorn, behöver jag en annan typ måste jag skriva kod för det den istället för att ha sådana här återanvändbara metoder as_bool. as_int, as_uint, as_int64, as_variant o.s.v som finns i arguments

// 2 argument objekt std::unordered_map<std::string, std::string> arguments_{ {"source", stringFile}, {"file-key", uKey} }; if (!stringSegment.empty()) { arguments_["segment"] = stringSegment; } auto result_ = COMMAND_ListLinesWithPattern(arguments_, vectorRegexPatterns, ptableLineListLocal.get());

Kommenterar ovanstående exempel
"vinsten" kanske inte tycks vara speciellt stor att göra något eget jämfört med att använda stl som i det alternativa exemplet, och det märks säkert inte speciellt den första månaden om man jämför mellan två team där ena använder DOD och andra mer traditionella lösningar. Så efter ett tag börjar ena teamet få problem om de inte ansträngt sig ordentligt för att hantera data flexibelt.

Att få utvecklare vet om det här tror jag beror på att oftast väljs ramverk och då ligger sådant här i ramverket, utvecklaren anpassar ramverket.

Har man flera olika typer av objekt som förstår varandras typsystem, då kommer de verkliga vinsterna.

Det här är också orsaken till att jag vet att mitt lilla sökprojekt kommer vinna över de flesta andra (i alla fall de som jag granskat koden på). De har optimerat hela förloppet för att det skall vara snabbt och inte tänkt på flexibilitet kring data. Tar för mycket tid för dem att bygga ut, blir hela omskrivningar.

Och runtime information är mycket säker programmering, slipper sitta och bråka med buggar. Inte så att det inte dyker upp buggar men eftersom det finns information i datat smäller det snabbt. Tiden mellan skriven kod tills det smäller är kort.

Permalänk
Medlem
Skrivet av Yoshman:

Är gd::argument med DOD på något sätt?

Japp, den är ordentligt optimerad efter uppgiften

Skrivet av Yoshman:

Ge ett exempel på vad det är för JSON-filer du behöver parse:a om det ens är relevant om det är 32 eller 48 bytes per nod?
Dels får man in >100k sådana noder i vissa moderna CPUers L2$, dels så lär den relativa skillnaden det utgör av den totala mängd data som man ändå behöver jobba med vara ett avrundningsfel. Eller?

Självklart spelar det här kanske mindre roll för 98% av alla som behöver json men BOOST är så generellt och de har högre krav. Ville bara peka på hur de inte kan göra vad som helst eftersom de har ett viktigare krav att det skall vara kompatibelt och fungera väl med standarder. Den begränsningen är ett ordentligt handikapp vid DOD

Permalänk
Medlem
Skrivet av orp:

Gällande extra typinfo så har jag fortfarande inte sett någon direkt poäng med det. Det enda jag kan tänka på är om man ska implementera variants( och när har man behov av variants?) eller när ett språk saknar templates, generics och macros som Go inledningsvis gjorde och man behövde använda reflections flitigt för att hantera typer någorlunda generiskt.

Om du decouple (som det heter), alltså anropar kod utan att koden vet om annan kod, mer att du sänder data mellan olika logik. där behöver du något format med typad information om det skall bli flexibelt

Permalänk
Datavetare
Skrivet av klk:

Japp, den är ordentligt optimerad efter uppgiften

Det var inte frågan. Frågan var om och i så fall hur den är optimerad för DOD. För kan överhuvudtaget inte ser det.

Möjligen kan det finnas funktioner som förenklar DDD, men det är inte samma sak som DOD.

Skrivet av klk:

Självklart spelar det här kanske mindre roll för 98% av alla som behöver json men BOOST är så generellt och de har högre krav. Ville bara peka på hur de inte kan göra vad som helst eftersom de har ett viktigare krav att det skall vara kompatibelt och fungera väl med standarder. Den begränsningen är ett ordentligt handikapp vid DOD

OK, så frågan kvarstår: kan du ge ett enda konkret exempel när det är viktigt att ha 32 bytes meta-data per nod jämfört med 48 bytes meta-data. Vad är det för typ av JSON-dokument man parsar då?

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 du decouple (som det heter), alltså anropar kod utan att koden vet om annan kod, mer att du sänder data mellan olika logik. där behöver du något format med typad information om det skall bli flexibelt

Nja, decoupling handla ju bara om att reducera sammanflätning mellan två (eller flera) kodblock(namespaces, moduler etc) med olika syfte. Det handlar ju snarare om att ha väldefinierade interface(inte OOP interface) mellan kodblocken. Du kan ju exempelvis ha en funktion som tar en struct eller en container(vec, hashmap) som argument då finns ju typinfon redan i argumentet utan att bidra till ytterligare coupling.

Den enda gången jag kan se det som aktuellt att ha en typinfon tillgänglig är om datan måste serialiseras pga att couplingen är över IPC eller liknande men är får du gärna fylla i vad du menar?

Permalänk
Medlem
Skrivet av Yoshman:

I C++ finns en poäng om man stoppar in typer med icke-trivial dtor i en union. I det läget måste man ha någon form av tag/typinfo för att veta vilken dtor som ska köras när unionen går out-of-scope.

Hade det inte varit bättre med en functionspekare till destruktorn istället för typinfo? Har för mig att det är så man löser det i glib i C. Jag har såklart väldigt begränsad uppfattning av C++ eftersom jag kodat ca 100 rader C++.

Enda gången jag har använt variants är just GDBus, misstänker att det blev lättare för implementationen att bara ta emot en container och instrospecta innehållet för marshalling och unmarhalling.

Skrivet av Yoshman:

Men känner också: antalet gånger jag känt behov av "variant" är väldigt få. Har använt det ett par gånger i Rust, kommer inte ihåg exakt till vad längre. Finns fall där variant kan ha sina poänger, men känns som specialfall och inte normalfall.

Spännande, kommer du ihåg vilket use-case? Personligen känns det som att tagged unions löser de flesta av dessa fallen.

Permalänk
Medlem
Skrivet av KAD:

Videon har bäring på de senaste sidorna i tråden mest för att Kelly i mångt och mycket totalslaktar sina egna tagged unions, till förmån för vad han kallar "encoding". Har man inte sett videon förut så ska man nog med stor behållning börja från början, annars börjar slakten vid ca 23:25 och fortsätter in i exemplen från kompilatorn.

https://www.youtube.com/watch?v=IroPQ150F6c

(det finns en skjortvideo från i år också, den är också lite intressant om man inte ser världen i svartvitt)

Det var väl ungefär där som jag slutade titta så jag får väl se den till sitt slut. Satt och kollade igenom några timmar med presentationer(så jag såg även Mike Actons) om DOD för att förstärka min bild inför diskussionen här. X_x

Permalänk
Skrivet av KAD:

Den här tråden går ju i cirklar offtopic, så mitt (totalt onödiga) bidrag för ikväll blir att återposta(?) Andrew Kellys utmärkta video om Data Oriented Design (tillämpad i Zig-kompilatorn, med exempel i språket).

Nu har jag också sett videon. Även om jag inte har använt DOD själv, så bekräftade den att min bild av DOD stämde ganska väl. Encoding-tricken som eliminerade en bool ur en struct (human_with_braces) fanns dock inte i min verktygslåda. Om size var viktigt hade jag använt bitfields för att stoppa in den i samma ord som taggen.

Intressant att hans omskrivningar gav så stora skillnader i Zig-kompilatorn. Men gissningsvis består vinsten till stor del av att han helt enkelt strök en massa medlemmar i strukturerna. Att byta pekare till index krymper datastrukturerna, men att plocka bort data som i de flesta fall inte används måste vara ännu effektivare.

Videon avfärdade helt och hållet uttalandet från @klk om att man som utvecklare blir mer effektiv om man använder DOD. Om man måste använda hjärncykler till att analysera hur saker och ting bäst skall sparas i minne och vad som är den mest gynnsamma encoding man kan komma på borde det rimligen betyda att det tar längre tid att skriva kod för den som kör DOD jämfört med en traditionell OOP-design.

Permalänk
Medlem

Lite relaterat till ovan: man sparar huvudbry om man håller sig till common sense. Att bara willy-nilly optimera lite random structar och förvänta sig någon mätbar prestandaökning är nog ett recept för besvikelse. Där optimeringar likt det som nämns i DOD-videon ovan faktiskt spelar roll är om det ligger i någon form av "kritisk loop" (och/eller massiva mängder data).

Visa signatur

Citera mig för svar.
Arch Linux

Permalänk

@Dimman Nej, nej, sunt förnuft har inget i denna tråden att göra. Här är det dogmatiskt tänkande och principfasthet som gäller. Att lyssna på motsidan och omvärdera sin världsbild, det är inget vi gör här.

Permalänk
Skrivet av Dimman:

Lite relaterat till ovan: man sparar huvudbry om man håller sig till common sense. Att bara willy-nilly optimera lite random structar och förvänta sig någon mätbar prestandaökning är nog ett recept för besvikelse. Där optimeringar likt det som nämns i DOD-videon ovan faktiskt spelar roll är om det ligger i någon form av "kritisk loop" (och/eller massiva mängder data).

Jo, det är därför lite förvånande att han får så stora vinster i kompilatorn. Nu vet jag inte vad de gör i Zig-kompilatorn, men traditionellt är just kompilatorer kända för att de är svåra att optimera. Det är många olika faser och man har väldigt oregelbundna accessmönster. Vissa analyser under optimeringarna är beräkningstunga, men man spenderar inga större delar av kompileringstiden i dem. Det finns normalt inga inga tydliga kritiska loopar utan det är helt enkelt många saker som skall göras och allt tar tid.

Jag ser inte att man skulle få några jättevinster av att extra data och lägga det i separata tabeller. I andra tillämpningar kan man effektivt loopa över data och tjäna på optimeringar som nu kan göras, @Yoshman nämner alltid SIMD, men som vore svåra att göra om data låg i objekten, OOP-style. Men i kompilatorn ser jag inte riktigt att man skulle göra sådana vinster, det är få fall man kommer gå linjärt genom det extraherade datat.

Här handlar det nog nästan enbart om att han minskat storleken på data. Det ser man effekter av i flera steg, först och främst blir det mindre minnestrafik eftersom kompileringen använder mindre data. Mindre data ger även bättre utnyttjande av cacharna vilket i sin tur ger ännu mindre minnestrafik.

Permalänk
Medlem
Skrivet av Yoshman:

Men känner också: antalet gånger jag känt behov av "variant" är väldigt få. Har använt det ett par gånger i Rust, kommer inte ihåg exakt till vad längre. Finns fall där variant kan ha sina poänger, men känns som specialfall och inte normalfall.

Det här förvånar mig faktisk lite med tanke på att Rust är ett språk som har mönstermatchning med kontroll av fullständighet. ADT (Algebraic Data Types) är ju ett fundamentalt byggblock. T.ex. för AST (Abstract Syntax Trees) för parsning eller interpreters för DSLs (Domain-Specific Languages), domänmodellering, Result och Option är ADTs, state machines, message passing/protokoll (som verkar vara det @kik delvis pratar om, om man kisar lite) osv… Är inte enums något som borde förekomma överallt?

Permalänk
Datavetare
Skrivet av orp:

Spännande, kommer du ihåg vilket use-case? Personligen känns det som att tagged unions löser de flesta av dessa fallen.

Se nedan, blev helt galet skrivet. Har aldrig använt en explicit variant-typ i Rust.

Skrivet av jclr:

Det här förvånar mig faktisk lite med tanke på att Rust är ett språk som har mönstermatchning med kontroll av fullständighet. ADT (Algebraic Data Types) är ju ett fundamentalt byggblock. T.ex. för AST (Abstract Syntax Trees) för parsning eller interpreters för DSLs (Domain-Specific Languages), domänmodellering, Result och Option är ADTs, state machines, message passing/protokoll (som verkar vara det @kik delvis pratar om, om man kisar lite) osv… Är inte enums något som borde förekomma överallt?

@ båda: inser att det jag skrev blev galet, blir kanske så efter en lång arbetsdag...

Menade att "använt enums i Rust med associerade värden på ett sätt som man i C++ skulle kunna göra med t.ex. variants". Fast även det har förkommit rätt sällan i min kod.

Majoriteten av fallen är "typen är känd compile-time". Det riktigt vanliga fallet med "tagged enum" är Option<T>, som är definierad som en enum med medlemmar Some(T) och None. Då är det i.o.f.s. en standardtyp, inget jag definierat själv.

Edit: det skrivet. Kan kanske ha mer att bero på domänerna jag jobbat med i Rust så här långt. Har precis utvecklat en YAML-parser i C#. Input YAML, output en objekt-graf där alla ingående noder är definierad av YAML. Om den hade skrivits i Rust hade det nog blivit väldigt naturligt med tagged enums! T.ex. vid distinktion av YAML sekvens, mapping och scalar.

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

Lite relaterat till ovan: man sparar huvudbry om man håller sig till common sense. Att bara willy-nilly optimera lite random structar och förvänta sig någon mätbar prestandaökning är nog ett recept för besvikelse. Där optimeringar likt det som nämns i DOD-videon ovan faktiskt spelar roll är om det ligger i någon form av "kritisk loop" (och/eller massiva mängder data).

Vi brukade analysera programdelar med matematik, där vi försökte fastställa någon form av värsta- och bästa-fallsscenarior.
Utifrån kapitel 9 (om Asymptotiskt uppträdande, Knuth skriver bara "Asymptotics") i Knuths "Concrete Mathematics" kan man göra komplexitetsanalys av funktioner och t.o.m. hela program (för att förstå och helt anamma kapitel 9 bör man förstås ha tagit till sig matematiken och informationen i de övriga kapitlen). Sådana analyser visar att oerfarna (ibland självlärda) programmerare är särskilt dåliga på att inse hur illa det kan bli och har heller ingen bra uppfattning om vad man kan göra åt dålig kod.
Den bra koden skrivs av erfarna, välutbildade programmerare, gärna med matematik i bakfickan men även där kan man hitta fällor och då är matematiken verktyget för att hitta ställena man kan effektivisera.

Det finns förstås andra böcker. Man kan söka på "Computational complexity".
Några som jag minns som bra böcker:
Homer & Selman: Computability and Complexity Theory, relativt ny book
Jones: Computability and Complexity, gammal (96?97?)
Sipser: Introduction to the Theory of Computation
Arora and Barak: Computational Complexity

Den här av Maheshwari & Smid hittade jag när jag försökte hitta titlar på böcker, brukar bara komma ihåg författarna (det är så de är organiserade på min server) och den är gratis att ladda ner.

Visa signatur

Debian, bara Debian, Debian överallt