Portál AbcLinuxu, 30. dubna 2025 14:36
Jedna komponenta bude držet tu mapu a třídit přicházející požadavky. Ale ty procesory (instance v mapě) můžou vznikat někde jinde, vytvoří je nějaká jiná komponenta, která třeba někde načte konfiguraci. A pak se můžou používat na třetím místě – jiná komponenta, která bude např. řídit vlákna a přehazovat úlohy mezi nimi.
Jasně, můžou se ty instance rozkopírovat, aby měl každý svoji, ale dejme tomu, že to nechci* dělat, protože při vytváření instance se třeba něco počítá nebo se získávají nějaká data (která už jsou po dobu existence instance konstantní, takže je lze sdílet i mezi vlákny). A další věc je, že jako komponenta, která vybere procesor v mapě podle klíče, už nemusí mít kontrolu nad tím, kdy daná úloha doběhla. Ty úlohy můžou běžet někde jinde a mezi tím přicházejí další a vybírají se pro ně procesory v mapě. A do toho může přijít požadavek na rekonfiguraci – přiřazení nového procesoru určitému klíči – a od té doby se budou úlohy s tímto klíčem zpracovávat v něm, ale ty staré mají doběhnout ještě v tom starém, jehož instanci nemůžu zrušit.
Ono ta myšlenka, že by data měl vždycky někdo vlastnit a starat se o ně, je sice hezká, ale ono to často nemusí být tak jednoduché – data můžou putovat skrz různé části (vrstvy či komponenty) programu – a kdo je pak vlastní? A co když se budou používat na více místech? Před časem jsem např. narazil na chybu v inotify-tools – tam taky nebylo jasné, kdo data vlastní a kdo by je měl uvolit (takže se program choval buď chybně nebo by unikala paměť a musela by se nějak pečlivě ručně uklízet, což u složitějšího programu může být dost problém – a to tohle byl úplně triviální případ a stejně v něm autor udělal chybu).
*) Pak existuje návrhový vzor muší váha (flyweight), kdy se třída rozdělí na dvě části, jedna je těžká a drží společný stav a druhá je lehká a lze ji libovolně (levně) rozkopírovat a držet v ní nějaký lokální stav (pro vlákno, aktuálně řešenou úlohu atd.). To často dobrý přístup, nicméně tenhle problém to nijak neřeší – pořád zůstává otázka, co s tou společnou těžkou částí – kdo se o ni bude starat a jak se rozhodne, že už je možné ji smazat?
To mi prijde jako hell z navrhu. Ty objekty si nemuze tvorit kdo si zachce. Tim se micha logika do vice vrstev.
Ja bych to resil asi tak, ze objekt by nebyl procesor, ale jedna uloha a po dokonceni by zavolala callback do spravce, aby si uklidil. Mapa na dispatch by obsahovala pointry na factory funkce. Takze pri rekonfiguraci by uloha pozadala dispatcher o zmenu factory funkce nejakeho typu requestu, v mape by se atomicky prepsal pointer a novy requesty uz by tvorily jinou ulohu.
To mi prijde jako hell z navrhu. Ty objekty si nemuze tvorit kdo si zachce. Tim se micha logika do vice vrstev.A proc by nemohl? Co kdyby jeden procesor obsluhoval blikani LED a druhy posilani emailu? Nedavalo by smysl, kdyby kazdy objekt pochazel z jineho modulu?
Ja bych to resil asi tak, ze objekt by nebyl procesor, ale jedna uloha a po dokonceni by zavolala callback do spravce, aby si uklidil. Mapa na dispatch by obsahovala pointry na factory funkce. Takze pri rekonfiguraci by uloha pozadala dispatcher o zmenu factory funkce nejakeho typu requestu, v mape by se atomicky prepsal pointer a novy requesty uz by tvorily jinou ulohu.A tobe prijde normalni, ze puvodni navrh, kde stacil jeden switch nebo v rozsirene variante jedna mapa a k tomu navrhovy vzor command, preroste do takto obludnych rube-goldbergovskych rozmeru? Kam se podel princip KISS? Mnohem jednodussi by bylo divat se na tu mapu jako na nejakou konfiguraci, ktera bude nemenna a predavat ji spolecne s jednotlivymi pozadavky. Pri zmene konfigurace pak vytvorit novou instanci mapy. Odpadnou ti problemy s atomickymi updaty a navic ziskas lepsi konzistenci a mnohem snazsi debugovani a testovani. Dalsim krokem by pak bylo pouziti copy-on-write jako optimalizace pro sdileni dat a na scenu by prisel i ten share_ptr.
Mnohem jednodussi by bylo divat se na tu mapu jako na nejakou konfiguraci, ktera bude nemenna a predavat ji spolecne s jednotlivymi pozadavky.
Tohle se mi líbí, protože to má nějakou přidanou hodnotu – úloha se zpracuje konzistentním způsobem (pokud bude mít víc kroků, tak se na všechny aplikuje jedna konfigurace, místo aby se pro každý krok v době jeho zpracování vzala v tu chvíli platná konfigurace). Ale přijde mi, že to situaci ještě víc komplikuje.
Ale přijde mi, že to situaci ještě víc komplikuje.V cem? Budes mit ctyri jasne definovane zodpovednosti. Dispatcher, konfigurace, procesory, pozadavky. Kazda ta trida bude jasne ohranicena a hlavne kod bude snadno testovatelny a debugovatelny. Navic ziskas srozumitelny kod i pro paralelni zpracovani bez zbytecnych zamku. Jestli se bude vzdy kopirovat cela mapa nebo se bude pouzivat nejaka variace na CoW, je jen implementacni detail.
v mape by se atomicky prepsal pointer a novy requesty uz by tvorily jinou ulohu.
A co když např. jeden procesor bude přičítat 1 a druhý 5? Budu to chtít řešit společným kódem, který se bude lišit jen parametrem. To se dá řešit třídou, od které si udělám dvě instance. Pokud by to byly funkce, tak se buď duplikoval kód nebo bych si musel napsat f1()
a f5()
, které by volali nějakou f()
s parametrem, ne? A stále tam bude ta 1 a 5 zadrátovaná v kódu – jenže já bych třeba chtěl, aby je uživatel mohl zadat v GUI nebo v konfiguraci.
Tak factory funkce muze delat uplne cokoliv. Tohle si predstavuju asi jako ze bych mel class AdderTask, kde bych v konstruktoru specifikoval, kolik ma teda pricitat. Factory funkce by z dostupnych dat stvorila konkretni objekt z obecnyho predpisu.
Kdybych chtel aby uloha byla soucasti nejakeho vetsiho celku, kde je potreba drzet nejaky globalnejsi state, tak zas jen factory vyrobi objekt s vazbou na ten spravce. Nebo napr. muze vracet uz existujici ulohy, pokud prichozi data patri jim.
template<typename T> Memory { T read<T>() { return Serializer<T>::read(); } void write<T>(const T& t) { Serializer<T>::write(t); } uint8_t* read(size_t size); void write(const uint8_t*); } // generická implementace případně např. omezit jen na čísla pomocí enable_if a SFINAE template<typename T> struct Serializer { static void write(const T& t, Memory& memory) { memory.write(reinterpret_cat<uin8_t>(&t), sizeof(T)); } static T read() { reinterpret_cat<T>(*memory.read(sizeof(T))); } } // specializace pro konkrétní typ template<> struct Serializer<X> { static void write(const X& x) { memory.write<TypeOfDataLength>(x.data_length); memory.write(x.data, x.data_length); } static X read() { const auto length = memory.read<TypeOfDataLength>(); return X(memory.read(length), length); } }
Jinymi slovy: Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.Nicméně cílem není napsat minimalistickou implementaci vlastního assembleru, ale spíš najít řešení použitelné pro větší programy, které bude trošku pružnější a nebude tam vše zadrátované v kódu. Viz komentář:... prostě cokoli, co se na základě nějakého klíče (nebo složitějšího mechanismu) vyhledá, předají se tomu nějaká data, ono je to zpracuje a vrátí nebo odešle jinam… a tyhle procesory dat se můžou v čase měnit, můžou vznikat, zanikat.
Nicméně cílem není napsat minimalistickou implementaci vlastního assembleru, ale spíš najít řešení použitelné pro větší programy, které bude trošku pružnější a nebude tam vše zadrátované v kódu.Jeden ze zakladnich principu ve vyuce je postupovat od konkretniho k abstraktnimu. A to by se melo odvijet i na tom projektu. Udelat prvni navrh, aby reseni fungovalo, a pak, az je to potreba, zavest dalsi abstrakce. To zda, je to potreba, zavisi od konkretni situace. Pro urcite situace tento switch muze byt naprosto dostacujici a cokoliv navic z toho bude delat bloatware. Dokazi si predstavit situaci, kdy kvuli omezeni hardwaru bude lepsi prejit na nejakou variantu computed-goto nebo primy preklad do strojoveho kodu. Dokazi si take predstavit, ze kvuli omezeni hardwaru (a prenositelnosti) bude lepsi zavest pro kazdy prikaz vlastni objekt a provazat to treba s alokaci registru. V momente, kdy od toho reseni ocekavas, ze to muze: "být cokoli, filtry e-mailu, obdoba servletů nebo prostě cokoli,", tak delat analyzu nema smysl, protoze ti z toho driv nebo pozdeji vyleze Lisp nebo jiny turingovsky uplny jazyk. Jinak ten navrh toho "assembleru" mi prijde ponekud nestastny. Zejmena proto, ze je u nej patrna inspirace skutecnymi "assemblery", napr. explicitni prace s daty na konkretnich adresach. Skutecne assemblery jsou vetsinou hrozne, protoze jejich navrh vychazi z fyzicke implementace procesoru nebo historicke zateze. U nove vytvoreneho "assembleru", ktery bude jen/prevazne interpretovan nema smysl kopirovat omezeni hardwaru a davalo by vetsi smysl si to navrhnout nejak elegantneji, treba jako zasobnikovy procesor, load/store architekturu.
Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.
Pěkné :-), ale na nízkoúrovňové věci se to moc nehodí. Tím C/C++ se zabývám pro případy, kde bych se i já zdráhal použít Javu. Pokud bych mohl použít Common Lisp, tak bych mohl použít i Javu a byl bych v tom mnohem efektivnější (což teď nemyslím jako srovnání těch jazyků ale spíš svých schopností, protože s Javou pracuji každý den a CL neznám skoro vůbec).
A to by se melo odvijet i na tom projektu. Udelat prvni navrh, aby reseni fungovalo, a pak, az je to potreba, zavest dalsi abstrakce.
Ten první návrh se switch
em jsem už právě udělal a funguje mi to, jak potřebuji. Psalo se mi to celkem dobře a ty šablony a reference mi umožnili pěkně zapsat to, co jsem chtěl (v Javě bych to dělal jinak, ale tady se mi tenhle způsob líbí).
A teď se právě dostáváme k druhému kroku – jakási simulace, že program je nějak napsaný a do toho přichází požadavek, že se má něco konfigurovat dynamicky a nemá to být zadrátované v kódu.
Pro urcite situace tento switch muze byt naprosto dostacujici a cokoliv navic z toho bude delat bloatware.
Klidně z toho ten bloatware nebo overengineering udělám, protože smyslem tohoto programu je vyzkoušet si různé přístupy. Zkoušet si to až na reálném projektu mi přijde riskantní a hloupé – pořád se bavíme o hodně základních věcech a kódu na pár (desítek, maximálně stovek) řádků, ne o návrhu, který bych psal měsíce do šuplíku, jen tak pro případ, že by se to jednou mohlo hodit.
V momente, kdy od toho reseni ocekavas, ze to muze: "být cokoli, filtry e-mailu, obdoba servletů nebo prostě cokoli,", tak delat analyzu nema smysl, protoze ti z toho driv nebo pozdeji vyleze Lisp nebo jiny turingovsky uplny jazyk.
Umění je v tom, udělat ten návrh právě přiměřeně pružný a univerzální – když to bude málo, tak to nebude vyhovovat požadavkům a bude se to muset pořád předělávat. A když to bude moc, tak se to zase bude špatně používat a vlastně můžeš dát uživatelům rovnou nějaký programovací jazyk a říct, jim, ať si to napíší sami – jenže pak nedodáváš software/aplikaci, ale jen nějakou platformu a klade to obrovské nároky na uživatele (musí umět programovat, místo aby si jen něco naklikal v GUI nebo nakonfiguroval v souboru).
Jinak ten navrh toho "assembleru" mi prijde ponekud nestastny. Zejmena proto, ze je u nej patrna inspirace skutecnymi "assemblery", napr. explicitni prace s daty na konkretnich adresach.
Těch příkazů může být až 256, mám jich rozmyšlených víc a budou tam i praktičtější/přímočařejší jako nastavení RGB na konkrétní hodnoty (ne přes adresu, ale hodnota bude hned v parametru). Ale ty adresy mají smysl pro případ, že tam bude nějaký cyklus, budou se inkrementovat hodnoty a pak to skočí vždy na ten stejný příkaz s tou samou adresou (ale pod ní už bude jiná hodnota, takže se barvy budou měnit).
Problém je s tím, že si ty adresy musíš odpočítat, což je opruz, ale je to optimalizované pro jednoduchost toho interpretu. Ten kód by člověk nemusel psát ručně v hexadecimálním editoru, ale může to dělat v nějakém nástroji, který mu ty adresy dopočítá – a pak z toho vypadne ta posloupnost bajtů, která se dá už snadno interpretovat i na tom jednočipu.
Pokud bych mohl použít Common Lisp, tak bych mohl použít i Javu a byl bych v tom mnohem efektivnější (což teď nemyslím jako srovnání těch jazyků ale spíš svých schopností, protože s Javou pracuji každý den a CL neznám skoro vůbec).Takze jeste vyklad toho aforismu:
It can also be interpreted as a satirical critique of systems that include complex, highly configurable sub-systems.[3] Rather than including a custom interpreter for some domain-specific language, Greenspun's rule suggests using a widely accepted, fully featured language like Lisp.
Klidně z toho ten bloatware nebo overengineering udělám, protože smyslem tohoto programu je vyzkoušet si různé přístupy. Zkoušet si to až na reálném projektu mi přijde riskantní a hloupé – pořád se bavíme o hodně základních věcech a kódu na pár (desítek, maximálně stovek) řádků, ne o návrhu, který bych psal měsíce do šuplíku, jen tak pro případ, že by se to jednou mohlo hodit.V tom pripade doporucuji jako cviceni naprogramovat interpreter Lispu/Scheme nebo Forthu. Je to otazka par desitek/stovek radku kodu a pouceni z toho bude nemale, viz vyse.
Umění je v tom, udělat ten návrh právě přiměřeně pružný a univerzální – když to bude málo, tak to nebude vyhovovat požadavkům a bude se to muset pořád předělávat. A když to bude moc, tak se to zase bude špatně používat a vlastně můžeš dát uživatelům rovnou nějaký programovací jazyk a říct, jim, ať si to napíší sami – jenže pak nedodáváš software/aplikaci, ale jen nějakou platformu a klade to obrovské nároky na uživatele (musí umět programovat, místo aby si jen něco naklikal v GUI nebo nakonfiguroval v souboru).Chapal bych, kdybys takovou hloupost pronesl na serveru abcwindowsu.cz, ale na abclinuxu.cz vetsina lidi vi, ze to tak neni. Musi uzivatel, ktery chce pouzivat shell, znat programovani? Ne, i kdyz je to plnohodnotny programovaci jazyk. Jdou v shellu napsat konfiguraky, ktere si bezny uzivatel upravi podle svych potreb? Ano. (I kdyz LP ma trochu jiny nazor.) Tak v cem je problem? Nejspis v tom si pripustit, ze na tom dat uzivateli plnohodnotny (ac sandboxovany) jazyk neni nic spatneho, ale docela dobry design pattern. V unixech naprosto bezny pattern. Vice viz The Art of Unix Programming.
Ale ty adresy mají smysl pro případ, že tam bude nějaký cyklus, budou se inkrementovat hodnoty a pak to skočí vždy na ten stejný příkaz s tou samou adresou (ale pod ní už bude jiná hodnota, takže se barvy budou měnit).Nechci ti kazit radost z objevovani, ale napovim, ze instrukcni sada, kde skoro kazda instrukce hrabe nekam do pameti, se pozdeji nejspis ukaze jako hodne nepruzne reseni, a ten preklad pro nejaky mikrontroler nebude nejefektivnejsi.
Ten kód by člověk nemusel psát ručně v hexadecimálním editoru, ale může to dělat v nějakém nástroji, který mu ty adresy dopočítá – a pak z toho vypadne ta posloupnost bajtů, která se dá už snadno interpretovat i na tom jednočipu.Povidej mi o tom...
Ten kód by člověk nemusel psát ručně v hexadecimálním editoru, ale může to dělat v nějakém nástroji, který mu ty adresy dopočítá – a pak z toho vypadne ta posloupnost bajtů, která se dá už snadno interpretovat i na tom jednočipu.Jak se to liší od přeflashování programu?
Např. ti přijde e-mail a na RGB panelu se má přehrát nějaká sekvence jako upozornění. Ty bys kvůli tomu pokaždé přeflashoval jednočip?
Tzn. ten nástroj použiješ jednorázově k vytvoření té sekvence a pak ji opakovaně používáš a už to jen přeposíláš jako posloupnost bajtů.
Např. ti přijde e-mail a na RGB panelu se má přehrát nějaká sekvence jako upozornění.To ale není rekonfigurace, ne?
Ty bys kvůli tomu pokaždé přeflashoval jednočip?Reagoval jsem na tu situaci, kdy chceš psát nástroj, který ti dopočítá adresy v tom kódu, který pak nahraješ do jednočipu. Tomu dopočítání adres se říká linkování a tomu nahrání do jednočipu flashování.
Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.Velmi brutálně řečeno, tak brutálně že by to mnoha uctívačům "moderního" programování mohlo způsobit otevřenou zlomeninu mozku
Nicméně cílem není napsat minimalistickou implementaci vlastního assembleru, ale spíš najít řešení použitelné pro větší programy, které bude trošku pružnější a nebude tam vše zadrátované v kódu. Viz komentář: Ve skutečnosti mi nejde o nějaké DSL nebo jazyky – může to být cokoli, filtry e-mailu, obdoba servletů nebo prostě cokoli, co se na základě nějakého klíče (nebo složitějšího mechanismu) vyhledá, předají se tomu nějaká data, ono je to zpracuje a vrátí nebo odešle jinam… a tyhle procesory dat se můžou v čase měnit, můžou vznikat, zanikat.Zadání nechápu. Nevidim vůbec podobu nějakého konkrétního výstupu. Tohle vypadá jako "Chci něco obecného, co bude něco obecně dělat s nějakými obecnými daty. Jak na to?"
Moje aktuální představa je, předělat ten switch na mapu (obsahující zřejmě shared_ptr) a kód v jednotlivých blocích switche přesunout do tříd, které budou implementovat (dědit) společné rozhraní. Některé třídy se budou používat ve více instancích – např. CMD_INCREMENT a CMD_DECREMENT můžou být implementované v jedné třídě, akorát se instanciuje s jiným parametrem (znaménkem).To bude asi enterprise assembler. Když to poběží na desktopu jako Java program, návrh to má jako Java program, správu paměti jako Java program, přístupuješ k tomu jako k Java programu... Proč to nenapíšeš v Javě?
Zadání nechápu. Nevidim vůbec podobu nějakého konkrétního výstupu.
Přepsal jsem to na tu mapu a třídy (viz zdrojáky). Už to celkem vypadá, jak jsem chtěl – je tam ten potenciál ty příkazy/konfiguraci měnit a není to tak zadrátované v kódu, jako dřív.
Turing complete jazyk vůbec nepotřeboval, bylo to spíše podobné přehrávání animací.
Jde mi o to, že potřebuješ buď velkou paměť na tom MCU, abys do toho nacpal celou animaci ve formě „neživých“ dat, a nebo potřebuješ udržovat trvalé spojení s počítačem (a na něm musí celou dobu běžet nějaký program), který to řídí a průběžně tam posílá jednotlivé příkazy. Případně třetí možnost: pokaždé přeflashovat ten jednočip novým programem. Proto mi přišlo zajímavé udělat ten interpret, kterému by šlo jednorázově z počítače poslat program v nějakém primitivním jazyce a jednočip by ho pak interpretoval, přičemž by na to stačilo méně paměti a přitom by to vygenerovalo více „entropie“ na těch LEDkách.
P.S. a ještě připomínám, že se tu bavíme ve dvou rovinách a) něco si zkouším v desktopovém C++ a snažím se na tom něco naučit pro případné další programy a b) firmware pro jednočip, který bude mít úplně jinou implementaci než ten desktop a společný bude asi jen ten binární formát/jazyk.
Přepsal jsem to na tu mapu a třídy (viz zdrojáky). Už to celkem vypadá, jak jsem chtěl – je tam ten potenciál ty příkazy/konfiguraci měnit a není to tak zadrátované v kódu, jako dřív.Tak jak to je teď napsané, tak ten
shared_ptr
je zbytečný a stačil by ti unique_ptr
.
Pokud se ti nelíbí ta deklarace té mapy s make_shared
, určitě by se to dalo řešit (třeba přidáním konstruktoru do té mapy, který by z nějak vhodně specifikovaného initializer_list
udělal ty instance i s pointery nebo něco).
Jde mi o to, že potřebuješ buď velkou paměť na tom MCU, abys do toho nacpal celou animaci ve formě „neživých“ dat, a nebo potřebuješ udržovat trvalé spojení s počítačem (a na něm musí celou dobu běžet nějaký program), který to řídí a průběžně tam posílá jednotlivé příkazy.Tohle bylo řešené s ESP8266, které má docela dost RAM (něco jako 96kB nebo taknějak). Ale i kdyby nemělo dost RAM, je to IIRC schopné za běhu zapisovat do flash, která má 1MB. Fungovalo to tak, že se k tomu člověk připojil telefonem, nahrál data a pak to běželo dál samo.
Proto mi přišlo zajímavé udělat ten interpret, kterému by šlo jednorázově z počítače poslat program v nějakém primitivním jazyce a jednočip by ho pak interpretoval, přičemž by na to stačilo méně paměti a přitom by to vygenerovalo více „entropie“ na těch LEDkách.Jsi si jistý, že to není předčasná optimalizace? Teoreticky máš asi pravdu v tom, že s procedurálním jazykem to asi můžeš stlačit o něco víc, nicméně je otázka, jestli ten rozdíl opravdu je natolik velký a stojí to za to. Taky je potřeba brát v úvahu, že taková animace bude asi oproti deklarativnímu zápisu náročnější na vytvoření (v podstatě potřeba naprogramovat) a to zejména má-li být ve výsledku menší než ta deklarativní. Btw. trochu mi to přípomíná TrueType Hinting language, to je taky takový DSL Turing complete jazyk.
Znám, dokonce se mi tu nějaké válí v šuplíku :-) Ale to je kanón na vrabce – chci použít ATmega328 s 2 KB RAM. WiFi tam fakt nechci – má to ovládat svítící logo na bedně počítače složené z deseti WS2812. V nejjednodušším případě to projede cyklus, inkrementuje proměnnou a podmíněným GOTO se to vrátí na začátek a třeba po třech opakováních ta podmínka přestane platit a program se ukončí. Další možnost jsou barevné přechody nebo nějaké posuny barev mezi diodami.
Ale to je kanón na vrabce – chci použít ATmega328 s 2 KB RAM. WiFi tam fakt nechci – má to ovládat svítící logo na bedně počítače složené z deseti WS2812.Záleží jakou metrikou. Co se týče rychlosti CPU a velikosti RAM, tak to jo, ale příkon AFAIK nemá větší (naopak se dá vyladit na extrémně nízký příkon) a fyzicky je mrňavý. Wifi může zůstat vypnutá (já ji zatím ani na nic reálného nepoužil). On ta menší rychlost a paměť u těch Atmelů je v dnešní době skoro spíše umělé omezení...
Tiskni
Sdílej:
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.