Rychlost ukládání

vložil Radek Červinka 5. listopadu 2010 23:48

V článku Základy JSON v Delphi kolega <z> nastínil porovnání rychlosti JSON, INI a dalších. Jelikož bylo několik dotazů pro upřesnění, zde je výsledek, který mi <z> zaslal a navíc testovací program. Je samozřejmé, že je zde prostor pro další optimalizaci, přesto to lze chápat jako ukázku a přehled možností.

Aktualizováno po druhé

Testovací projekt ke stažení nová verze

Nový test obsahuje jak zápis, tak čtení. To se samozřejmě projevilo ve výsledcích.

Testy jasně prokázaly, že JSON je znatelně nejrychlejší, ještě více se to projeví při ukládání většího množství dat. S JSON lze uložit 100 000 položek a pořád to bude 2x rychlejší, než uložení 2 000 položek pomocí TIniFile)

Nový test a výsledky

  • vytvoření objektu
  • zapsání 2000 položek typu string
  • zapsání 2000 položek typu integer
  • uložení do souboru
  • uvolnění objektu
  • vytvoření a načtení objektu ze souboru
  • 500x změna položek typu string na pozici 700
  • znovu uložení do souboru a uvolnění objektu
metodadobavelikostvytížení procesoru
json-SuperObject62ms65776b0%
json-SuperObject63ms81778b0% (formátovany výstup)
lkJSON78ms65778b0%
lkJSON655ms81780b0% (formátovany výstup)
NativeXML8580ms83608b0%
MSXML36s95634b0%
TIniFile4384ms57784b100%
TMemINI12012ms57786b100%
TStringList9017ms57775b35%
THashedStringList11950ms57775b100%

Poznámka Radek Červinka: Pro mne je nejzajímavější rozdíl mezi TStringList a THashedStringList, kdy jsem očekával, že THashedStringList bude rychlejší. Po pohledu do zdrojáků jsem ale pochopil, že v tomto případě to není pravda, jelikož se jedná o aktualizace dat a THashedStringList po každém přidání zneplatňuje hash tabulku a v případě vyhledávání v tomto okamžiku je nutné vytvořit hashe znovu - což se stane.

Aktualizace 2: THashedStringList by se neměl používat - jedná se jen o dinosaura. Pokud máte Delphi 2009+ použijte TDictionary protože je nejlepší.

Pokud by se ale jednalo o naplnění a pak jen prohledávání, tak by THashedStringList byl sice slušné řešení, ale na TDictionary se dívá i tak z velké dálky.

Tagy: ,

Praxe

Komentáře

6.11.2010 8:40:46 #

pepak

Zarazil mě výsledek THashedStringListu, protože se mi zdá fakt divné, že by implementace hashovací tabulkou měla být pomalejší než implementace sekvenčním přístupem. Nemluvě o rozdílu mezi TStringListem a TMemIniFile, když, pokud si dobře vzpomínám, TMemIniFile interně používá TStringList. Tak jsem se podíval do zdrojáku:

1) Celý test je výrazně pokřivený svou metodikou: NIKDE neparsuje uložený textový soubor a dokonce ani NIKDE nezkouší jakékoliv čtení z už zparsovaných dat. Zabývá se výhradně tím, že do komponenty napumpuje spoustu záznamů a pak to celé zapíše na disk. V reálném použití ale pravděpodobně bude čtení mnohonásobně převažovat nad zápisy, zvlášť kritické pak bude prvotní rozparsování vstupu při startu aplikace.

2) Jenom taková estetická připomínka: Pořadí jednotlivých testovacích případů ve zdrojáku hodně stěžuje porovnání, proč se to chová divně.

3) Vysvětlení výsledků TStringListu a THashedStringListu je prosté: první část (vystvoření 4000 položek) je v nich udělané úplně blbě - výhodně pro rychlost, ale naprosto v nesouladu s tím, jak by se komponenty pro ukládání hodnot skutečně používaly. Mimo jiné už mě je jasné, proč je takový rozdíl mezi TMemIniFile a TStringList: zápisy typu TStringList.Add('a=b') sice probíhají v čase O(1), ale takhle se to skutečně nedá používat. Bylo nezbytné použít TStringList.Values['a'] = 'b', protože takhle bude s parametry pracovat aplikace. Nezkoušel jsem to, ale jsem si dost jistý, že za těchto okolností se vyrovná rychlost TStringListu a TMemIniFile, a pokud není implementace THashedStringListu úplně idiotská (v mých Delphi není, takže se nemůžu podívat), měl by být THashedStringList o několik řádů rychlejší než obě předchozí komponenty.

4) Podobnou unfair výhodu dostal TNativeXml - při vytváření 4000 položek se rovnou zakládají nové elementy, komponenta se vůbec nemusí zabývat otázkou, jestli náhodou už požadovaný element neexistuje a nejde tedy o UPDATE místo INSERTu (v SQL terminologii). Obě JSON komponenty se s tímhle musí vypořádat.

5) Bylo by zajímavé vyzkoušet, do jaké míry jsou dosažené časy tvořené jednotlivými kroky v benchmarku. Mám třeba vážné podezření, že se ukáže významný rozdíl v práci SuperObjectu a lkJSON čistě v tom, že v SuperObjectu se soubor zapisuje přes string -> stringstream -> filestream, zatímco v lkJSON přes string -> TStringList.Text (pomalé!! Musí se rozparsovat do jednotlivých řádků) -> TStringList.SaveToFile (pomalé!! napřed se řádky zase spojí do jednoho stringu) -> filestream. Nevidím důvod, proč se v obou případech nemohl použít stejný způsob zápisu na disk. Takhle samoúčelná změna ty výsledky dost devalvuje, protože porušuje pravidlo "za jinak stejných podmínek" - to je skoro, jako kdybych XML zapisoval na ramdisk, JSON na disketu a z výsledků usuzoval, že XML je rychlejší.

Závěr: Z takto provedeného testu NELZE NA NIC USUZOVAT, za prvé proto, že není dodržena srovnatelnost podmínek, za druhé proto, že nejsou zohledněny všechny významné skutečnosti (parsování, čtení), no a za třetí proto, že ten test se ani vzdáleně neblíží reálnému použití. (Je to asi na té úrovni, že bych srovnával výkon grafických karet s tím, že každá je strčená do jinak výkonného počítače s jiným operačním systémem a celý test byl založen na tom, že vyzkouším pomocí GDI vykreslit sto miliard vyplněných obdélníků - dá to nějaké výsledky, podle kterých jde karty seřadit, ale ty výsledky jsou nesmyslné a neplní účel testu).

pepak

6.11.2010 10:35:31 #

<z>

Nebudu se s vami hadat, na vsem bude trochu pravdy, dokonale to urcite nebude ;)

Ale tento test vychazi 100% z realnych podminek ... ve svem programu jsem potreboval 1x cist (pri startu) a zapisovat asi 100x - a asi si dovedete domyslet, co to udela za paseku, kdyz sem k tomu pouzival MemINI. To, ze jsem to mel takhle implementovany, je riziko amateru :)
Srovnatelnost neni mozna dokonala, ale zakladni predstavu o rychlostech jednotlivych trid urcite da. Za rychlosti JSON si budu stat, zadnou optimalizaci nedojde k 10nasobnemu zrychleni TStringList nebo jinych komponent tak, aby to dohnaly ;)

Problem s THashedStringList nedokazu vysvetlit, nikdy jsem ho nejak moc nepouzival a navic myslim, ze jeho implementace v Delphi neni uplne vyborna (podle toho, co jsem cetl).

Problem s TMemIniFile muze byt dan tim, ze se tam vytvari sekce, ktera pouziva THashedStringList ;)

ad 1) to je dobry napad, to bych mohl pridat
ad 2) delal jsem to puvodne pro osobni  ucely, tak ono to tam casem pribyvalo
ad 3) podivam se na to
ad 4) zkontroluji, dostuduji :)
ad 5) nad tim jsem nepremyslel, ale z neznameho duvodu SuperObject pri ukladani pres TStringList byl nejak zpomaleny, tak jsem to nahradil ...

Dekuji za vycerpavajici odezvu, najdu si odpovidajici cas a pokusim se vyladit tento test.

<z>

7.11.2010 0:32:36 #

radekc

Článek byl aktualizován

radekc

7.11.2010 7:54:07 #

pepak

Bohužel, námitky vůči nedodržení ceteris paribus ("za jinak stejných podmínek") trvají. Vezmu-li SuperObject jako základ:

1) lkJSON:
- Při testu zápisu nepochopitelně používá kombinaci IndexOf + Add tam, kde by měl používat "replace" (neznám přímo název metody - soudě podle zdrojáku asi nejspíš Field[...].neco). V případě tohoto testu (ale nikoliv v reálném použití!) to sice vede ke stejnému výsledku, ale jinou cestou a nedá se vyloučit, že to takhle je pomalejší.
- Test zápisu pracuje s Variantem tam, kde SuperObject pracuje se stringem.

2) NativeXml:
- Stejně jako lsJSON nepracuje s replace ale s nalezením nodu a případným vytvořením nového.

3) MSXML:
- Stejná výhrada jako u NativeXML.
- Neprovádí se u něj test aktualizace, aniž by to bylo kdekoliv zmíněno! To se nám to pak dosahuje skvělých časů...

4) THashedStringList:
- Jak říkám, neznám přesnou implementaci. Ale velice bych se divil, kdyby se to opravdu chovalo tak, jak píše Radek v komentáři ("jelikož se jedná o aktualizace dat a THashedStringList po každém přidání zneplatňuje hash tabulku") - to by signalizovalo skutečně BRUTÁLNÍ chybu v implementaci, protože u normálně implementované hashtabulky žádná taková potřeba neexistuje. Nepomohlo by třeba vhodné nastavení maximálního počtu záznamů při inicializaci tabulky?
- Nakonec si snad ten test budu muset vyzkoušet sám - ať se na to dívám jak chci, kód pro správně napsanou hash tabulku a pomocí ní implementovaný MemIniFile podle mě musí být rychlejší než jakákoliv alternativní verze včetně momentálně vedoucího JSONu - prostě proto, že bude mít nejrychlejší parsování vstupu, stejně rychlý zápis výstupu a nejrychlejší nebo přinejhorším stejně rychlé vyhledání požadovaného záznamu.

pepak

7.11.2010 11:02:21 #

radekc

Jen u toho MSXML je 36s ne 36ms, tj.36000ms. Nevím zda je v jeho případě test OK, protože se mi to zdá moc - i když to tak opravdu vypadá.

ad THashedStringList - je to třída asi optimalizovaná pro INI (proto je v inifiles) a není podle mne zas až tak dobrá. viz.
https://forums.embarcadero.com/message.jspa?messageID=86963
kde je několik alternativ a nalezených problémů.

radekc

7.11.2010 11:07:56 #

radekc

Jen ještě poznamenám, že THashedStringList je určitě i v Delphi 2007

"c:\Program Files\CodeGear\RAD Studio\5.0\source\Win32\rtl\common\IniFiles.pas"

radekc

7.11.2010 11:24:14 #

<z>

problem je u tech knihoven, ze nemaj "replace" ... a ja kvuli testu ty knihovny opravdu prepracovavat nebudu, jednak protoze se mi nechce a taky protoze na to nemam potrebne znalosti ;)

- u lkJSON proste pole Field pouzit nejde, protoze tu polozku nevytvori ... tak musim pomoci IndexOf zjistit, jestli tam je (i kdyz neni, protoze to SuperObject dela) a pak pridat
- lkJSON nepracuje s variantem

- u NativeXml a MSXML to stejne, proste ty funkce jsem tam nenasel
- u MSXML je to 36 sekund ;)

- THashedStringList ma opravdu tuto brutalni chybu, trochu jsem hledal na netu a nasel jsem, ze hodne lidi si muselo udelat svoji implementaci prave kvuli tomu ...

PS: jestli najdes neco rychlejsiho, nez JSON, tak ti opravdu pogratuluji ;)

<z>

7.11.2010 11:26:36 #

<z>

aaa, moje chyba ... u MSXML jsem hledal, jak se tam pracuje s polozkama, tak jsem to okomentoval a pak na to zapmel - nic to ale nemeni na tom, ze to je strasne pomaly :D

<z>

7.11.2010 17:42:38 #

pepak

Co je mi to platné, když mám Delphi 5 :-)

pepak

7.11.2010 17:45:31 #

pepak

No hledat to nebudu, protože mě při mých počtech konfiguračních parametrů rozdíl v rychlosti ukládání nezajímá (je mi jedno, jestli to potrvá jednu milisekundu nebo deset), ale jsem si naprosto jistý, že dobře implementovaná hashtabulka bude ještě rychlejší než JSON. Prostě proto, že bude mít jednodušší parsování a jen jednu úroveň záznamů (žádný strom prvků).

pepak

7.11.2010 21:09:35 #

radekc

No jelikoz uz jsi nekolikrat rekl, ze mas i Delphi 2009 tak proto.

radekc

7.11.2010 22:35:06 #

pepak

Jo, _v_práci_ :-)

Zítra se na to podívám, nebo si aspoň stáhnu zdroják pro prozkoumání doma, ale teď na víkend mi ta informace fakt nepomohla :-)

pepak

8.11.2010 0:09:14 #

<z>

nemuzu poprit, ze hashlist by mel byt srovnatelne rychly ... uz proto, ze JSON je jedna z takovych spravnych implementaci ;)

a nic nebrani tomu pridat do testu dalsi implementace, aby i ostatni meli prehled, jake maji moznosti

<z>

8.11.2010 8:25:41 #

pepak

Tedy skutečně zírám, jak špatně je THashedStringList napsaný. Nevěřil bych, že je něco takového možné.

pepak

8.11.2010 9:47:54 #

radekc

On je to nejaky starý pozůstatek a asi by se to němelo používat (asi je tam jen pro kompatibilitu). Místo toho použij TDictionary z jednotky Generics.Collections, který používá hashe a je opravdu rychlý a dobře napsaný. Delphi 2009+

radekc

Komentování ukončeno

Naše nabídka

MVP
Ing. Radek Červinka - Embarcadero MVP
profil na linkedin, Twitter:@delphicz

Nabízím placené poradenství a konzultace v oblasti programování a vývoje SW.
Dále nabízíme i vývoj speciálního software na zakázku.

Neváhejte nás kontaktovat (i ohledně reklamy).

love Delphi

O Delphi.cz

Delphi je moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64, Mac OSX, Linux a na iPhone a Android.

Delphi.cz je nezávislý portál pro uživatele Delphi. Portál není koncipován pro úplné začátečníky, i když i ti se zde nebudou nudit, ale spíše na programátory, kteří již něco znají a chtějí své znalosti dále rozvíjet a sledovat novinky.

Poslední komentáře

Comment RSS

Dle měsíců