[ Skip to the content ]

Institute of Formal and Applied Linguistics Wiki


[ Back to the navigation ]

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
Next revision Both sides next revision
treex:api-implementation [2015/12/11 11:06]
popel
treex:api-implementation [2015/12/11 12:51]
popel
Line 21: Line 21:
 V Perlu zabírá string (o 0-15 jednobajtových znacích) 56 bajtů, ale když dám stringy do pole, tak se to zaokrouhlí na 64 bajtů. V Perlu zabírá string (o 0-15 jednobajtových znacích) 56 bajtů, ale když dám stringy do pole, tak se to zaokrouhlí na 64 bajtů.
 Int zabírá 24 bajtů a v poli 32 bajtů. Int zabírá 24 bajtů a v poli 32 bajtů.
-Když dám stringy/inty do hashe (místo do pole), tak je to taky 64/32 bajtů, jen je tam samozřejmě ještě nějaká režie na ten hash. Viz+Když dám stringy/inty do hashe (místo do pole), tak je to taky 64/32 bajtů, jen je tam samozřejmě ještě nějaká režie na ten hash, takže to vychází 128/96 bajtů. Viz
  
   perl -MDevel::Size=:all -E 'my $s="s"; say total_size {map {$_=>$s} 1..1_000_000}' # string 128M   perl -MDevel::Size=:all -E 'my $s="s"; say total_size {map {$_=>$s} 1..1_000_000}' # string 128M
Line 27: Line 27:
   perl -MDevel::Size=:all -E 'my $s="s"; say total_size {map {$_=>\$s} 1..1_000_000}'# ref     96M   perl -MDevel::Size=:all -E 'my $s="s"; say total_size {map {$_=>\$s} 1..1_000_000}'# ref     96M
  
-Reference v Perlu zabírá stejně jako int (v čku na 64bitech má typicky pointer 8 bajtů, int 4 bajty a long long int 8 bajtů, což je vše samozřejmě mnohem míň než v Perl).+Reference v Perlu zabírá stejně jako int (a to těch 24 bajtů samostatně, 32 bajtů poli, 96 bajtů v hashi). 
 +(V Céčku na 64bitech má typicky pointer 8 bajtů, int 4 bajty a long long int 8 bajtů, v poli to zůstává stejné, v hashi přibude režie dle míry naplnění tabulky, ale Céčku se objekty nedávají do hashe, leda snad wild atributy.)
 Z hlediska rychlosti by bylo lepší ukládat přímo referenci na string (místo intu, kterým by se pak muselo indexovat pole). Z hlediska rychlosti by bylo lepší ukládat přímo referenci na string (místo intu, kterým by se pak muselo indexovat pole).
 Ušetřil bych 32 bajtů na každém stringovém atributu (a pokud by měl ten string víc než 15 znaků, tak ještě víc) a navíc bych potřeboval paměť pro slovník, která je ale (díky zipfovskému rozdělení lemmat, na větších dokumentech) zanedbatelná. Ušetřil bych 32 bajtů na každém stringovém atributu (a pokud by měl ten string víc než 15 znaků, tak ještě víc) a navíc bych potřeboval paměť pro slovník, která je ale (díky zipfovskému rozdělení lemmat, na větších dokumentech) zanedbatelná.
Line 44: Line 45:
  
 ==== Benchmark Perlích accessorů ==== ==== Benchmark Perlích accessorů ====
-  Zápis i čtení +Nejdřív jsem porovnal různé implementace a měřil kolikrát za sekundu se provede zápis následovaný čtením (stejného) atributu. H=hash-based object. A=array-based object. LV=lvalue (viz níže). 
-  LV V::Magic H        64759/s  +  LV V::Magic H         64 759/s  
-  LV V::Magic A        74649/s  +  LV V::Magic A         74 649/s  
-  LV Sentinel H       169605/s  +  LV Sentinel H        169 605/s  
-  LV Sentinel A       170267/s  +  LV Sentinel A        170 267/s  
-  Moose              1155163/s  +  Moose              1 155 163/s  
-  object H           1193837/s  +  object H           1 193 837/s  
-  object A           1233004/s  +  object A           1 233 004/s  
-  LV C::XSAccessor H 2880212/s  +  LV C::XSAccessor H 2 880 212/s  
-  Mouse              3111529/s  +  Mouse              3 111 529/s  
-  LV C::XSAccessor A 3111529/s  +  LV C::XSAccessor A 3 111 529/s  
-  C::XSAccessor H    3558344/s  +  C::XSAccessor H    3 558 344/s  
-  Moops              3688969/s  +  Moops              3 688 969/s  
-  Moo                3738065/s  +  Moo                3 738 065/s  
-  O::Tiny::RW::XS    3764547/s  +  O::Tiny::RW::XS    3 764 547/s  
-  C::XSAccessor A    4228110/s  +  C::XSAccessor A    4 228 110/s  
-  hash               4969215/s  +  hash               4 969 215/s  
-  array              5386997/s +  array              5 386 997/s 
  
 +Pak jsem vybral několik implementací a porovnával zápis a čtení zvlášť
   Zápis atributu   Zápis atributu
   H-lv-check-sentinel    714 566/s   H-lv-check-sentinel    714 566/s
Line 82: Line 84:
   A-direct            10 180 350/s   A-direct            10 180 350/s
  
-  Čtení atributu:  +  Čtení atributu: 
-  h-get_lemma_perl  2 453 219/s +  h-lv_check_sentinel  1 147 836/s 
-  a-get_lemma_perl  2 572 440/s +  a-lv_check_sentinel  1 147 836/s
-  h-lv_lemma_perl   3 206 187/s +
-  a-lv_lemma_perl   3 406 574/s+
   ---   ---
-  h-lv_lemma_xs     5 825 422/s +  h-get_lemma_perl     2 453 219/s 
-  a-lv_lemma_xs     6 412 374/s +  a-get_lemma_perl     2 572 440/s 
-  h-get_lemma_xs    6 859 843/s +  h-lv_lemma_perl      3 206 187/s 
-  a-get_lemma_xs    7 489 828/s+  a-lv_lemma_perl      3 406 574/s
   ---   ---
-  h-direct         10 144 688/s +  h-lv_lemma_xs        5 825 422/s 
-  a-direct         12 112 263/s+  a-lv_lemma_xs        6 412 374/s 
 +  h-get_lemma_xs       6 859 843/s 
 +  a-get_lemma_xs       7 489 828/s 
 +  --- 
 +  h-direct            10 144 688/s 
 +  a-direct            12 112 263/s
  
 +Co z toho plyne?
 +  * Všechny čtení i zápisy jsou zřejmě "dostatečně rychlé na Perl" (krom lvalue přes ''Variable::Magic'', ale to jsem hned po prvním experimentu zavrhl ve prospěch [[https://metacpan.org/pod/Sentinel|Sentinel]]).
 +  * Pole jsou jen o málo rychlejší než hashe. Možná by se rozdíl zvětšil, kdybych měl v objektu více atributů než jeden, ale víc než 16 jich asi mít nechceme (''iset'' bude objekt držící všechny features).
 +  * Čtení je podobně rychlé jako zápis atributu obdobnou metodou.
 +  * Nejrychlejší je přímý přístup do hashe/pole (tedy ''$node->{lemma}="ahoj"'' a ''$node->[0]="ahoj"''), ale to lze používat jen uvnitř implementace, zvnějšku chceme samozřejmě encapsulation.
 +  * XS implementace jsou asi dvakrát rychlejší než perlové (a to v těch perlových dělám, ''$_[0]->{lemma} = $_[1];'' -- kdybych udělal ''my ($self, $new_lemma)=@_;'', tak to bude ještě pomalejší).
 +  * Největší otázka z hlediska API je, zda povolit **lvalue accessory**, tedy abych mohl jako v Pythonu psát ''$node->lemma = 'ahoj';'' místo ''$node->set_lemma('ahoj');''.
 +    * Obzvlášť užitečné je použití typu ''$node->lemma =~ s/a/b/;''.
 +    * V Perlu to není moc zvykem (byť to byla jedna z hlavních motivací uživatelských lvalue funkcí).
 +    * Dokud při zápisu nechci dělat nic jiného (kontroly, logování,...) než uložit tu hodnotu, tak jsou lvalue accessory dost rychlé (pro zápis dokonce se zdá rychlejší), navíc mohou být v XS.
 +    * Jakmile chci nějakou kontrolu v setteru (třeba že ord je kladný integer nebo deprel je jen z těch povolených), tak nemůžu použít XS implementaci (ledaže bych si ji napsal sám v Céčku). I oproti pure-perl řešení je to samozřejmě pomalejší, právě o čas strávený tou kontrolou (v *check* metodách výše jsem testoval jen, že to není undef).
 +    * Pokud chci dělat kontroly (či logování atd.) u lvalue accessoru, musím použít (něco jako) [[https://metacpan.org/pod/Sentinel|Sentinel]]. To už je třikrát pomalejší než (pure-perl) řešení přes setter.
 +    * Hlavní problém vidím v tom, že se mi pak zpomaluje i čtení atributu, při jehož zápisu je lvalue-check. Celé to dělám proto, aby se to "hezky" používalo, takže chci mít jednu metodu "lemma", která bude fungovat jako property (půjde z ní číst i do ní zapisovat). Kdybych měl dvě metody, tak to ztrácí smysl. Jenže v Perlu je Sentinel lvalue udělané tak, že se vrátí skalár, který má v sobě "magic", takže když se do něj **pak** přiřadí nějaká hodnota, tak se ve skutečnosti zavolá nějaká metoda (která provede tu kontrolu, logování...). A Perl neví, když se volá "lemma", zda se výsledek bude jen číst, nebo do něj i zapisovat, takže to neumí optimalizovat. Zde je Python jednoznačně lepší, protože properties jsou součástí návrhu objektů (byť jsem je zatím nebenchmarkoval).
 +    * Když bych tedy v Perlu používal lvalue accessory, tak jakmile se rozhodnu přidat nějakou kontrolu/logování při zápisu, tak se mi **čtení zpomalí pětinásobně** (oproti XS implementaci). Kdybych nepoužíval lvalue, tak čtení zůstane rychlé, protože getter je jiná metoda než setter (předpokládám, tedy že názvy setterů mají dle PBP prefix "set_").
 +    * Přidáním kontroly/logování se lvalue zápis zpomalí sedminásobně, zatímco bez lvalue (tj. setter a getter) se zápis zpomalí jen dvojnásobně. Tedy použitím lvalue se zápis s kontrolou/logováním **zpomalí trojnásobně**. Samozřejmě pokud by ta kontrola/logování dělala něco složitějšího než kontrolu undefu, tak se asi rozdíl mezi lvalue accessorem a setterem ztratí.
 +    * To 3x zpomalení při zápisu by mi nevadilo. Ale to 5x násobné zpomalení při čtení je nepříjemné, protože čtení bývá mnohem častější než zápis.
 +    * Na druhou stranu je otázka, zda se podaří zrychlit zbytek Treexu natolik, aby se projevil rozdíl v řádu 200 **nano**sekund na čtení jednoho atributu (pesimisticky předpokládejme, že všechny atributy mají kontrolovaný/logovaný zápis, což snad nebude pravda). Odhaduji, že na parsing (plus tagging, nebo načtení z CoNLL-U) jedné věty (20 slov) potřebuji číst tak 1000 krát číst a 100 krát zapsat nějaký atribut, tedy s lvalue ztratím 200 **mikro**sekund. Přitom samotný parsing trvá aspoň několik **mili**sekund.

[ Back to the navigation ] [ Back to the content ]