2009-09-07 11 views
7

W poniższym module przykładowym pobierające i ustawiające są generowane przez dodanie anonimowych podprogramów do tabeli symboli. Po stworzeniu metod w ten sposób, czy powstały kod będzie funkcjonalnie równoważny (pod względem zachowania, szybkości itp.) Z modułem z ręcznie zapisanymi metodami pobierającymi i ustawiającymi, czy też takie podejście ma jakąś nieodłączną odpowiedzialność? (Zrobiłem kilka podstawowych prędkości porównawczych i nie wykryto żadnych różnic tak daleko.)W Perlu istnieją wady generowania modułów pobierających i ustawiających, a nie ich kodowania?

package Module;  
use strict; 
use warnings; 

BEGIN { 
    my @attr = qw(author title number); 
    no strict 'refs'; 
    for my $a (@attr){ 
     *{__PACKAGE__ . "::get_$a"} = sub { $_[0]->{$a}   }; 
     *{__PACKAGE__ . "::set_$a"} = sub { $_[0]->{$a} = $_[1] }; 
    } 
} 

sub new { 
    my $class = shift; 
    bless { @_ }, $class; 
} 

1; 
+0

'* {" get_ $ a "} = sub' ... powinno również działać. (Nie trzeba mieć "__PACKAGE__" tam) –

Odpowiedz

8

Nie powinno być żadnej różnicy w wydajności wykonawczego jeśli otrzymany kod jest taki sam w obu przypadkach. Zwykle nie jest to możliwe, chyba że używasz ciągu eval do tworzenia podprogramów. Na przykład kod, który podałeś:

... = sub { $_[0]->{$a} }; 

będzie zawsze tak nieco wolniej niż kod byś napisany ręcznie:

sub foo { $_[0]->{'foo'} } 

po prostu dlatego, że pierwszy ma dostać wartość Instrumentu zmienna $ a przed użyciem jej jako klucza do mieszania, podczas gdy późniejsza używa stałej jako swojego klucza mieszającego. Ponadto, na marginesie, shift zwykle jest szybszy niż $_[0].Oto niektóre kodu odniesienia:

use Benchmark qw(cmpthese); 

package Foo; 

sub manual_shift { shift->{'foo'} } 
sub manual_index { $_[0]->{'foo'} } 

my $attr = 'foo'; 

*dynamic_shift = sub { shift->{$attr} }; 
*dynamic_index = sub { $_[0]->{$attr} }; 

package main; 

my $o = bless { foo => 123 }, 'Foo'; 

cmpthese(-2, { 
    manual_shift => sub { my $a = $o->manual_shift }, 
    manual_index => sub { my $a = $o->manual_index }, 
    dynamic_shift => sub { my $a = $o->dynamic_shift }, 
    dynamic_index => sub { my $a = $o->dynamic_index }, 
}); 

a wyniki na moim systemie:

    Rate dynamic_index manual_index dynamic_shift manual_shift 
dynamic_index 1799024/s   --   -3%   -4%   -7% 
manual_index 1853616/s   3%   --   -1%   -4% 
dynamic_shift 1873183/s   4%   1%   --   -3% 
manual_shift 1937019/s   8%   4%   3%   -- 

Są tak blisko, że różnice mogą zgubić się w hałas, ale na wielu próbach myślę zobaczysz że wariant "ręcznej zmiany" jest najszybszy. Ale tak jak w przypadku wszystkich mikroprocesorów takich jak ten, musisz przetestować swój dokładny scenariusz na swoim sprzęcie i swojej wersji perla, aby mieć pewność czegokolwiek.

I tutaj ciąg eval wrzucony do miksu.

eval "sub eval_index { \$_[0]->{'$attr'} }"; 
eval "sub eval_shift { shift->{'$attr'} }"; 

Powinny być dokładnie takie same, jak warianty "ręczne", plus lub minus szum statystyczny. Moje wyniki:

    Rate dynamic_index manual_index dynamic_shift manual_shift eval_shift eval_index 
dynamic_index 1820444/s   --   -1%   -2%   -3%  -4%  -5% 
manual_index 1835005/s   1%   --   -1%   -2%  -3%  -4% 
dynamic_shift 1858131/s   2%   1%   --   -1%  -2%  -3% 
manual_shift 1876708/s   3%   2%   1%   --  -1%  -2% 
eval_shift 1894132/s   4%   3%   2%   1%   --  -1% 
eval_index 1914060/s   5%   4%   3%   2%   1%   -- 

Znowu te są tak blisko, że trzeba by podjąć wiele trudu i wykonywać wiele prób, aby uporządkować sygnału od szumu. Ale różnica między używaniem stałej jako klucza haszującego i używaniem zmiennej (której wartość musi najpierw zostać pobrana) jako klucz hash, powinna być widoczna. (Optymalizacja shift jest osobnym problemem i jest bardziej prawdopodobne, że zmieni się tak czy inaczej w poprzednich lub przyszłych wersjach języka Perla.)

+0

Twój eval_index potrzebuje $ in $ _ [0] escaped. – ysth

+1

Jeśli zamierzasz generować accessory, możesz równie dobrze wykorzystać generator FAST. Cf. http://search.cpan.org/dist/Class-XSAccessor To będzie szybsze niż cokolwiek innego niż bezpośredni dostęp do hasha. – tsee

+0

ysth: Dzięki, myślę, że backslash został zjedzony podczas edycji lokalnie. Poprawiłem to. –

7

Nie ma różnicy, ponieważ:

sub Some_package::foo { ... } 

jest tylko skrótem:

BEGIN { *Some_package::foo = sub { ... } } 

Odsyłacz z perlmod

2

Oba podejścia skutkują zainstalowaniem subwoofera rutynowe odniesienie do tablicy symboli w czasie kompilacji. Wydajność i wydajność środowiska wykonawczego będą dokładnie takie same. Może występować bardzo mała (tj. Pomijalna) różnica w czasie kompilacji.

Podobnym podejściem jest generowanie akcesorów na żądanie za pośrednictwem AUTOLOAD, co ma niewielki wpływ na środowisko wykonawcze. Korzystanie z podejścia AUTOLOAD może również zmienić zachowanie takich rzeczy, jak $object->can().

Oczywiście, metody generowania ukryją je przed jakąkolwiek formą analizy statycznej, w tym narzędzi takich jak ctags.

+1

Poważnie. Nie używaj AUTOLOAD do takich rzeczy. Otwiera bramy powodziowe na szaleństwo. (Jestem pewien, że ty, MC, wiesz, że :) – tsee

+3

** 'OSTRZEŻENIE:' ** Nie używaj 'AUTOLOAD', chyba że wiesz ** dokładnie ** co robisz. –

2

Zachowanie i wydajność środowiska wykonawczego powinny być prawie takie same (chyba, że ​​zrobisz coś, co dba o to, czy metody są zamknięte, czy nie).

Przy dużej liczbie atrybutów będzie różnica w czasie kompilacji i używaniu pamięci ... zarówno na korzyść wygenerowanych programów pobierających i ustawiających, a nie na te napisane ręcznie. Spróbuj na przykład w ten sposób:

BEGIN { 
    no strict 'refs'; 
    for my $a ("aaaa".."zzzz"){ 
     *{__PACKAGE__ . "::get_$a"} = sub { $_[0]->{$a}   }; 
     *{__PACKAGE__ . "::set_$a"} = sub { $_[0]->{$a} = $_[1] }; 
    } 
} 
print `ps -F -p $$`; # adjust for your ps syntax 

porównaniu

sub get_aaaa { $_[0]->{aaaa}   } 
sub set_aaaa { $_[0]->{aaaa} = $_[1] } 
sub get_aaab { $_[0]->{aaab}   } 
... 
sub set_zzzy { $_[0]->{zzzy} = $_[1] } 
sub get_zzzz { $_[0]->{zzzz}   } 
sub set_zzzz { $_[0]->{zzzz} = $_[1] } 
print `ps -F -p $$`; # adjust for your ps syntax 
2

Jedyna różnica to czas rozruchu. Dla prostych schematów generowania kodu różnica będzie trudna do zmierzenia. W przypadku bardziej złożonych systemów może się sumować. Jest to świetny przykład tego działania: Moose. Moose tworzy dla ciebie niesamowite rodzaje kodu, ale ma znaczący wpływ na czasy uruchamiania. Jest to wystarczający problem, że twórcy Moose pracują nad a scheme to cache generated code w plikach pmc i ładują je zamiast regenerować kod za każdym razem.

Weź również pod uwagę coś takiego, jak Class::Struct. Generuje kod za pomocą string eval (ostatnio sprawdzałem). Mimo to, ponieważ jest bardzo prosty, nie powoduje znacznego spowolnienia podczas uruchamiania.

6

Główną wadą dobrze generowanych akcesorów jest to, że pokonują narzędzia, które opierają się na analizie statycznej. Na przykład autouzupełnianie metody IDE. Jeśli jest to część dużego projektu, serdecznie polecam przyjrzeć się Moose. Jest to generacja akcesorów zrobiona poprawnie (i wiele więcej). Jest na tyle popularny, że do IDE dodawane jest wsparcie, dzięki czemu wspomniany problem zniknie w odpowiednim czasie.

Istnieje wiele generatorów dodatków na CPAN, które są łatwe w użyciu i generują umiarkowanie wydajny kod. Jeśli wydajność jest problemem, wtedy - pod warunkiem, że trzymasz się metod accessor - nie można uzyskać szybciej niż Class::XSAccessor, ponieważ używa on wysoce zoptymalizowanego kodu C/XS dla akcesorów.

Rolowanie własnego kodu generującego akcesorium jest najgorszą ze wszystkich opcji. Na zawsze pokonuje analizę statyczną, jest raczej trudna do odczytania i potencjalnie wprowadza nowe błędy.

2

Oprócz doskonałych punktów, o których wspominali inni, chciałbym dodać także główną wadę, którą znalazłem: wszystkie one pojawiają się jako anonimowe podprogramy w profilerze. Z jakiegokolwiek powodu, Devel::DProf po prostu nie wie, jak wymyślić nazwę.

Teraz chciałbym nadzieję, że, że nowszy Devel::NYTProf może wykonać lepszą pracę - ale nie próbowałem go.

Powiązane problemy