Vlákno názorů ke článku Minimalistický http server v C++ od kutr - @Ondřej Novák >> pole místo map Takže typická předčasná optimalizace....

  • 1. 10. 2018 22:52

    kutr (neregistrovaný)

    @Ondřej Novák

    >> pole místo map
    Takže typická předčasná optimalizace. Možná rychlejší řešení náchylná k chybám použítá místo standardních věcí. Přitom zbytek kódu není optimalizovaný vůbec (IMHO největší výkonostní propad bude spíš ve spouštění a zavírání vlákna pro každé spojení než v hledání v seřazeném poli vs. RB stromu). A i kdybys na tom trval, tak to řazení by stejně bylo lepší udělat jednou při inicialiaci a nespoléhat na programátora.

    >> constexpr
    constexpr má výhodu v tom, že překladač vůbec nemusí alokovat proměnnou a může jí všude inlinovat (compile time constant). U static const to obecně nepůjde.

    >> šablona RAII
    Problém RAII šablony je v tom, že nevhodně kombinuje procedurální C a objektový C++ přístup. Ten deskriptor socketu a souboru je typově nerozlišitelný i přestože označuje různý typ. Programátor to musí hlídat stejně jako v C. Kromě toho vytváří zbytečný stav, kdy může být nenainicializovaný. Třída socket a filedescriptor s metodami read/write, s open v constructoru a close v destructoru bude znamenat zhruba stejně kódu, ale bude jasně vidět co se tam děje a zmizí mezistav neotevřeného souboru/socketu. Navíc oproti standardním pointerům nebo scope guardům je ta šablona hodně nedotažená. Jinak to vysvětlení je zase ukázkový příklad předčasné optimalizace.

    >> static
    Není pravda co píšeš, anonymní namespace má od C++11 "internal linkage". Navíc podle standardu bylo tohle použití static chvíli označeno jako deprecated, ale nakonec zůstává kvůli kompatibiltě s C (např. protože nemůžeš vytvářet lokální typy). MSVC není zrovna dobrá reference standardu a ten kód je stejně psaný pro unix.

    >> operátor()
    Pokud by se ta funkce jmenovala podle toho co dělá, tak si toho asi nevšimnu. Takhle to zbytečně znepřehledňuje kód a není k tomu žádný důvod.

  • 2. 10. 2018 0:00

    Ondřej Novák

    > Takže typická předčasná optimalizace. Možná rychlejší řešení náchylná k chybám použítá místo standardních věcí.

    KDE je to náchylné k chybám? No a CO je na tom nestandardního? Hlavním důvodem této volby je ZEJMÉNA ušetřit si psaní No jen si to vemte. Kdybych zvolil mapu, znamená to beztak deklarovat seznam dvojic, což už tam mám. Dále bych musel deklarovat promenou typu std::map a v nějaké inicializaci do ní nakopírovat ten seznam. A nakonec vlastní vyhledávání bude naprosto stejné psaní, jen se podívej na to místo, kde se to používá. Místo std::lower_bound, tam bude mapa.find(). Ale zbytek bude naprosto stejný. Takže namísto abych se s tím takhle trápil jsem prostě vzal to pole, už seřazený a použil std::lower_bound, což je imho standardní stejně jako mapa. Navíc s bonusem toho, že to zabírá méně místa, a je rychlejší, protože nemusí dereferencovat pointery, ale používá půlení se stejnou složitostí.

    Seřadit 10 položek lze velice rychle pohledem. Seřadit 100 položek do zdrojáku lze snadno příkazem sort z terminálu. Pokud je to něco neměnné, statického, tak tohle je pro mě přímější cesta.

    >> constexpr

    Teď jste to vy, kdo předčasně optimalizuje. Pokud je mi známo, tak běžně překladač pozná, zda se u static const má zakládat proměnná nebo ne. Pokud vím, tak constexpr jen vyžaduje výpočet během kompilace. To rozšíření constexpr na static const a jestě k tomu inline je sice až v C++17 (což bych tedy pravda asi měl využít), ale obecně mám nedůvěru k věcem, kterým se často mění význam a tohle je jedna z těch věcí.

    >> šablona RAII

    Opět je to spíš důsledek lenosti psaní. Abych ty deskriptory měl plnohodnotné, musel bych všechny třídy deklarovat i se všema metodama a wrapovat je na posixové funkce. A to na to jsem línej. Takže se ty deskriptory používají prostě namísto číselných intů v posixových funkcí přímo. Nač bych se měl zabývat tím, že udělám plnohodnotnou třídu Socket se všemi funkcemi? Jen abych dosáhl nějaké 100% úrovně čistoty? Nehledě na to, že to moc dobře nejde, protože v unixovém světě je všechno deskriptor, takže bych stejně neuměl rozlišit soubor od soketu. Spoustu věcí je společných, spoustu věcí ne. Ale pokud máte víc času , tak si nad tím postavte třídní hierarchii.

    Jinak krásně ukazujete, že jste kód zas dobře nestudoval. Onen zbytečný stav "neinicializovaný" není zbytečný, protože jinak by vám nefungoval move constructor, který musí do toho objektu "odkud" se přesouvá něco dát, aby to se to pak destruktor nezkoušel zdestruovat. Navíc neplatný stav je též součástí posixových deskriptorů, takže, proč ho tam nepřiznat. Když zavoláte funkci socket() a ona vám vrátí -1, tak je to neplatný deskriptor a já snadno poznám, že je něco blbě.

    Jistě bych mohl zavolat
    auto s = std::make_uni­que<Socket>(AF_I­NET,....)
    a v konstruktoru vyhazovat výjimku když to selže.
    a vůbec si s tím pohrát.
    dokonce bych mohl udelat auto s = std::make_sha­red<Socket>(.­..) a měl bych ho sdílenej!
    ale výsledkem by byl delší kód, nevím jestli by byl o dost přehledný a byl by samozřejmě pomalejší a nenažranější. Ale možná bych se cítíl víc jako C++ guru ... možná.

    C++ jazyk je krásný právě tím, že nepředepisuje, jak se věci mají dělat. A prostě toto řešení mi přišlo rychleji naprogramované. A to zejména

    >> static

    Někdo vám nahoře napsal, že to právě ještě není vyřešeno, takže až to norma vyřeší, tak to začnu používat po novu. Zatím pořád anonymní namespace má external linkage, bohužel.

    >> operátor()

    Třída se jmenuje SplitString - je to málo popisné? Operátor () by se mohl jmenovat get_next().

    Kdo zná trošku můj kód, tak právě funktory často používám a též doporučuju všem. Protože mají velice univerzální použití. Pokud mám funkci, která čte nějaký data v nějakém formátu, nejlepší je, když ten vstup dostane jako T (šablona) a ona sama pak to T chápe jako funkci, kterou když zavolá, obdrží další položku ze vstupu. Takto to najdete v jakémkoliv kódu, který kdekoliv mám. SplitString je přesně dtto. Funguje jako funkce, která vrací další hodnotu ze vstupu.

    Je to trošku procedurální programování, trošku si hraní s lambdami, ale to mi přesně vyhovuje. Lambdy a funkce, které mají stav, miluju tuhle část normy.

    Ale jistě mohl bych napsat funkci split_string, která vrací vektor rozsekaného řetězce - a už jsme opět u toho, zase z toho je moloch, který alokuje někde nějaký buffer takže je to pomalé a komplikované

    Ad předčasná optimalizace? Ale nesmysl... to jsou osvědčené postupy, nad kterými prostě nepřemýšlím, nesedím u toho a neříkám, "tak jak to zoptimalizujeme". Je to ve stylu "tohle vždycky fungovalo nejlépe, tak to použijeme". Třeba použití std::string_view - jistě by každý všude měl std::string, ale - předčasná optimalizace, ušetříme alokaci tím, že budeme vytvářet pohledy na stringy - a já jsem rád, že se to do normy dostalo, protože to nepovažuju za předčasnou optimalizaci, ale za normální způsob programování. Tohle tam mělo být dávno.