अपने परीक्षण में, मैंने देखा है कि आंतरिक एक्सेसर विधियों (FETCH और FETCHSIZE) का उपयोग करने के रूप में बंधे हुए सरणियों पर पुनरावृत्ति सबसे अच्छा आधा है। निम्नलिखित बेंचमार्क इस मुद्दे को दिखाता है:

{package Array;
    sub new {
        my $class = shift;
        tie my @array, $class, [@_];
        \@array
    }
    sub TIEARRAY {
        my ($class, $self) = @_;
        bless $self => $class;
    }
    sub FETCH     {$_[0][$_[1]]}
    sub FETCHSIZE {scalar @{$_[0]}}
}

use List::Util 'sum';
use Benchmark 'cmpthese';

for my $mag (map 10**$_ => 1 .. 5) {

    my $array = Array->new(1 .. $mag);
    my $tied  = tied(@$array);
    my $sum   = sum @$array;

    print "$mag: \n";
    cmpthese -2 => {
        tied => sub {
            my $x = 0;
            $x += $_ for @$array;
            $x == $sum or die $x
        },
        method => sub {
            my $x = 0;
            $x += $tied->FETCH($_) for 0 .. $tied->FETCHSIZE - 1;
            $x == $sum or die $x
        },
        method_while => sub {
            my $x = 0;
            my $i = 0; $x += $tied->FETCH($i++) while $i < $tied->FETCHSIZE;
            $x == $sum or die $x
        },
        method_while2 => sub {
            my $x = 0;
            my $i = 0;
            $x += tied(@$array)->FETCH($i++) 
                while $i < tied(@$array)->FETCHSIZE;
            $x == $sum or die $x
        },
        method_while3 => sub {
            my $x = 0;
            my $i = 0;
            while ($i < tied(@$array)->FETCHSIZE) {
                local *_ = \(tied(@$array)->FETCH($i++));
                $x += $_
            }
            $x == $sum or die $x
        },
    };
    print "\n";
}

1000 के सरणी आकार के साथ, बेंचमार्क रिटर्न:

1000: 
                Rate   tied method_while3 method_while2 method_while   method
tied           439/s     --          -40%          -51%         -61%     -79%
method_while3  728/s    66%            --          -19%         -35%     -65%
method_while2  900/s   105%           24%            --         -19%     -57%
method_while  1114/s   154%           53%           24%           --     -47%
method        2088/s   375%          187%          132%          87%       --

मैंने अन्य रनों को छोड़ दिया है क्योंकि सरणी का आकार सापेक्ष गति में सार्थक परिवर्तन नहीं करता है।

method निश्चित रूप से सबसे तेज़ है, क्योंकि यह प्रत्येक पुनरावृत्ति पर सरणी के आकार की जांच नहीं करता है, हालांकि method_while और method_while2 बंधे हुए सरणी पर उसी तरह से काम कर रहे हैं जैसे for लूप, फिर भी धीमा method_while2 बंधे हुए सरणी से दोगुना तेज़ है।

यहां तक ​​​​कि $_ के स्थानीयकरण और method_while2 को method_while3 में अलियास्ड असाइनमेंट जोड़ने से बंधी हुई सरणी की तुलना में 66% तेज निष्पादन होता है।

for लूप में कौन-सा अतिरिक्त कार्य हो रहा है जो method_while3 में नहीं हो रहा है? क्या बंधे हुए सरणी की गति में सुधार करने का कोई तरीका है?

8
Eric Strom 1 अप्रैल 2011, 23:58
method और भी तेज़ होगा यदि पर्ल for 0 .. Y लंबाई Y की एक सरणी पूर्व निर्माण न करके अनुकूलित कर सकता है। यदि Y एक साधारण स्केलर नहीं है तो ऑप्टिमाइज़र को आसानी से मूर्ख बनाया जा सकता है। my $size = $tied->FETCHSIZE - 1; $x += $tied->FETCH($_) for 0 .. $size आज़माएं और देखें कि क्या यह बेहतर प्रदर्शन करता है।
 – 
Schwern
4 अप्रैल 2011, 08:10

3 जवाब

सबसे बढ़िया उत्तर

बेंचमार्क में, आप करते हैं

... for @$array;

क्या हुआ अगर तुमने किया था

++$_ for @$array;

यह काम करेगा। टाई मैजिक FETCH द्वारा लौटाए गए मान को एक लैवल्यू में लपेटता है जब वैल्यू को लैवल्यू संदर्भ में वापस किया जाता है। आप इसे डेवेल :: पीक का उपयोग करके देख सकते हैं।

use Devel::Peek;
Dump( $array->[2] );

SV = PVLV(0x14b2234) at 0x187b374
  REFCNT = 1
  FLAGS = (TEMP,GMG,SMG,RMG)
  IV = 0
  NV = 0
  PV = 0
  MAGIC = 0x14d6274
    MG_VIRTUAL = &PL_vtbl_packelem
    MG_TYPE = PERL_MAGIC_tiedelem(p)
    MG_FLAGS = 0x02
      REFCOUNTED
    MG_OBJ = 0x14a7e5c
    SV = IV(0x14a7e58) at 0x14a7e5c
      REFCNT = 2
      FLAGS = (ROK)
      RV = 0x187b324
      SV = PVAV(0x187c37c) at 0x187b324
        REFCNT = 1
        FLAGS = (OBJECT)
        STASH = 0x14a842c       "Array"
        ARRAY = 0x0
        FILL = -1
        MAX = -1
        ARYLEN = 0x0
        FLAGS = (REAL)
    MG_LEN = 2
  TYPE = t
  TARGOFF = 0
  TARGLEN = 0
  TARG = 0x187b374

FETCH द्वारा लौटाए गए मान को एक जादुई SV में लपेटना और उस जादू को संसाधित करना कम से कम कुछ अंतर के लिए है।

tied_rvalue => sub {
    my $x = 0;
    $x += $_ for @$array;
    $x == $sum or die $x
},
tied_lvalue => sub {
    my $x = 0;
    $x += $array->[$_] for 0 .. $tied->FETCHSIZE - 1;
    $x == $sum or die $x
},

100:
                 Rate tied_rvalue method_while3 tied_lvalue method_while2 method_while method
tied_rvalue    3333/s          --          -33%        -36%          -50%         -58%   -77%
method_while3  4998/s         50%            --         -4%          -25%         -36%   -66%
tied_lvalue    5184/s         56%            4%          --          -23%         -34%   -65%
method_while2  6699/s        101%           34%         29%            --         -15%   -55%
method_while   7856/s        136%           57%         52%           17%           --   -47%
method        14747/s        342%          195%        184%          120%          88%     --
3
ikegami 3 अप्रैल 2011, 08:59
+1, दिलचस्प, मैंने यह नहीं माना था कि पर्ल को संभावित बाद में कॉल को STORE पर संभालने के लिए चर पर जादू लागू करने की आवश्यकता है।
 – 
Eric Strom
4 अप्रैल 2011, 18:52

हर बार जब आप बंधी हुई सरणी का उपयोग करते हैं, तो उसे बंधी हुई वस्तु को देखना होता है, फिर विधियों को देखना होता है, फिर उन्हें लागू करना होता है। अपने अन्य संस्करणों के साथ, आप कुछ या सभी लुकअप या तो संकलन समय पर या प्रत्येक एक्सेस के बजाय लूप से पहले एक बार कर रहे हैं।

(method और अन्य method_* संस्करणों के बीच गति की तुलना इसका एक अच्छा उदाहरण है, वैसे: आप उस FETCHSIZE को करने का खर्च देख रहे हैं, यहां तक ​​कि बंधे हुए भी ऑब्जेक्ट पहले ही देखा जा चुका है। अब उस लागत को हर एक ऑपरेशन पर लागू करें जो सरणी को छूता है।)

7
geekosaur 2 अप्रैल 2011, 00:02
method_while3 कुछ भी प्री-कैचिंग नहीं कर रहा है, और ऐसा लगता है कि वह सब कुछ करता है जो फ़ोरैच लूप करता है, लेकिन अभी भी 66% तेज़ है।
 – 
Eric Strom
2 अप्रैल 2011, 00:06
मुझे लगता है कि यह कुछ कैशिंग कर रहा है: स्पष्ट लूपिंग कोड के साथ, पर्ल का अनुकूलक देख सकता है कि क्या हो रहा है, और यहां तक ​​​​कि यह भी दिया गया है कि FETCHSIZE को लूप से बाहर नहीं निकाला जा सकता है (क्योंकि यह यह नहीं बता सकता कि आप बाहरी संसाधन तक नहीं पहुंच रहे हैं जिसका आकार गतिशील रूप से बदल सकता है), यह यह देख सकता है कि tied कॉल को केवल एक बार करने की आवश्यकता है। मुझे नहीं लगता कि यह for @$array के लिए होता है क्योंकि आंतरिक आप और अनुकूलक दोनों से छिपे हुए हैं। (पर्ल 5 में अनुकूलन मुश्किल है, इसे हल्के ढंग से रखने के लिए, इसलिए कई मामलों में यह कोशिश भी नहीं करता है।) बीटीडब्ल्यू, यह अत्यधिक पर्ल संस्करण निर्भर है।
 – 
geekosaur
2 अप्रैल 2011, 00:19
मैंने tied(@$array)->method को my $get_tied = sub {tied(@{$_[0]})}; से बदल दिया और फिर $get_tied->($array)->method किसी भी प्रकार के कैशिंग को विफल करने के लिए, और यहां तक ​​कि प्रत्येक पुनरावृत्ति में उन दो अतिरिक्त सबरूटीन कॉलों को जोड़ना अभी भी for लूप की तुलना में 25% तेज है :(
 – 
Eric Strom
2 अप्रैल 2011, 00:38

यह ध्यान देने योग्य है कि local बहुत धीमा है, जो कुछ प्रदर्शन हानि के लिए मेथड_जबकि 3 बनाम अन्य मेथड बेंचमार्क के लिए जिम्मेदार है। यह एक ब्लॉक एंट्री और एक्जिट भी कर रहा है जिसकी लागत है।

भले ही method_while3 प्रोग्रामेटिक रूप से statement for x..y के बराबर है, पर्ल लूप के लिए आप से बेहतर अनुकूलित कर सकता है। अंगूठे के नियम के रूप में, जितना अधिक कोड आप पर्ल के अंदर करते हैं उतना तेज़ आपका कोड होगा।

3
Schwern 4 अप्रैल 2011, 08:16