Perl 中字符串的最快校验位例程是啥?

Posted

技术标签:

【中文标题】Perl 中字符串的最快校验位例程是啥?【英文标题】:What is the fastest check digit routine for a string in Perl?Perl 中字符串的最快校验位例程是什么? 【发布时间】:2011-06-15 09:17:59 【问题描述】:

给定一串数字,我必须使用 Perl 尽快对所有数字求和。

我的第一个实现是用 unpack() 解包数字,然后用 List::Utils 的 sum() 对数字列表求和。 它非常快,但是否有更快的打包/解包方法来完成这项任务?

我尝试了打包/解包组合,并对这两种实现进行了基准测试。 使用的 CPU 时间几乎相同;也许有一些我不知道的快速技巧?

这是我做基准测试的方法:

#!/usr/bin/env perl

use 5.012;
use strict;
use List::Util qw/sum/;
use Benchmark qw/timethese/;

timethese ( 1000000, 
    list_util => sub 
        my $CheckDigit = "999989989";
        do 
            $CheckDigit = sum( unpack( 'AAAAAAAAA', $CheckDigit ) );
         while ( $CheckDigit > 9 );
    ,
    perl_only => sub 
        my $CheckDigit = "999989989";
        do 
            $CheckDigit = unpack( '%16S*', pack( 'S9', unpack( 'AAAAAAAAA', $CheckDigit ) ) );
         while ( $CheckDigit > 9 );
    ,
 );

【问题讨论】:

也许是Inline::C?您可以使用 SvIV 宏将 Perl 标量变量转换为 C int。 在这种情况下,我总是会想到 C(和 Inline::C),但非常谦虚,我认为 List::Util sum() 是迄今为止最快的实现。跨度> 【参考方案1】:

unpack 不是拆分字符串的最快方法:

#!/usr/bin/env perl

use strict;
use List::Util qw/sum/;
use Benchmark qw/cmpthese/;

cmpthese ( -3, 
    list_util => sub 
        my $CheckDigit = "999989989";
        do 
            $CheckDigit = sum( unpack( 'AAAAAAAAA', $CheckDigit ) );
         while ( $CheckDigit > 9 );
    ,
    unpack_star => sub 
        my $CheckDigit = "999989989";
        do 
            $CheckDigit = sum( unpack( '(A)*', $CheckDigit ) );
         while ( $CheckDigit > 9 );
    ,
    re => sub 
        my $CheckDigit = "999989989";
        do 
            $CheckDigit = sum( $CheckDigit =~ /(.)/g );
         while ( $CheckDigit > 9 );
    ,
    split => sub 
        my $CheckDigit = "999989989";
        do 
            $CheckDigit = sum( split //, $CheckDigit );
         while ( $CheckDigit > 9 );
    ,
    perl_only => sub 
        my $CheckDigit = "999989989";
        do 
            $CheckDigit = unpack( '%16S*', pack( 'S9', unpack( 'AAAAAAAAA', $CheckDigit ) ) );
         while ( $CheckDigit > 9 );
    ,
    modulo => sub 
        my $CheckDigit = "999989989";
        $CheckDigit = ($CheckDigit+0) && ($CheckDigit % 9 || 9);
    ,
 );

生产:

                 Rate perl_only list_util       re unpack_star    split   modulo
perl_only     89882/s        --      -15%     -30%        -45%     -54%     -97%
list_util    105601/s       17%        --     -17%        -35%     -45%     -97%
re           127656/s       42%       21%       --        -21%     -34%     -96%
unpack_star  162308/s       81%       54%      27%          --     -16%     -95%
split        193405/s      115%       83%      52%         19%       --     -94%
modulo      3055254/s     3299%     2793%    2293%       1782%    1480%       --

所以split 看起来是您最好的选择,如果您必须将字符串拆分为字符。

但反复将数字相加是almost the same as taking the number mod 9(正如mirod 指出的那样)。不同之处在于 $Digits % 9 产生 0 而不是 9。修复它的一个公式是 ($Digits-1) % 9 + 1,但(至少在 Perl 中)不适用于全零情况(它产生 9 而不是 0)。在 Perl 中有效的表达式是 ($Digits+0) && ($Digits % 9 || 9)。第一项处理全零情况,第二项处理正常情况,第三项处理 0 到 9。

【讨论】:

看来模数可以很好地完成任务;看看我必须实现的内容: "... 如果结果(数字总和)小于 9,那么它是校验位,而如果它是 9,则校验位是 0;如果结果大于 9重复...“为什么他们一开始不要求模数,我不知道。 @Marco,这不是您实现的算法。如果校验位从不是 9,那么计算就是$Digits % 9。我不知道他们为什么不这么说。 是的,校验位永远不会是 9。我在发布的基准测试中省略了一些行,以保持对读者的影响。谢谢cjm!【参考方案2】:

在打包/解包方面不要太聪明并使用简单的拆分,或者在数学上稍微聪明并使用模数,这比所有其他方法都更糟?

#!/usr/bin/env perl

use strict;
use List::Util qw/sum/;
use Benchmark qw/timethese/;

my $D="99949596";

timethese ( 1000000, 
    naive => sub 
        my $CheckDigit= $D;
        do 
            $CheckDigit = sum( split//, $CheckDigit );
         while ( $CheckDigit > 9 ); 
    ,  
    list_util => sub 
        my $CheckDigit = $D;
        do 
            $CheckDigit = sum( unpack( 'AAAAAAAAA', $CheckDigit ) );
         while ( $CheckDigit > 9 );
    ,
    perl_only => sub 
        my $CheckDigit = $D;
        do 
            $CheckDigit = unpack( '%16S*', pack( 'S9', unpack( 'AAAAAAAAA', $CheckDigit ) ) );
         while ( $CheckDigit > 9 );
    ,
    modulo => sub 
        my $CheckDigit = $D % 9;
    ,
 );

结果:

Benchmark: timing 1000000 iterations of list_util, modulo, naive, perl_only...
 list_util:  5 wallclock secs ( 4.62 usr +  0.00 sys =  4.62 CPU) @ 216450.22/s (n=1000000)
    modulo: -1 wallclock secs ( 0.07 usr +  0.00 sys =  0.07 CPU) @ 14285714.29/s (n=1000000)
            (warning: too few iterations for a reliable count)
     naive:  3 wallclock secs ( 2.79 usr +  0.00 sys =  2.79 CPU) @ 358422.94/s (n=1000000)
 perl_only:  6 wallclock secs ( 5.18 usr +  0.00 sys =  5.18 CPU) @ 193050.19/s (n=1000000)

【讨论】:

“温和的 matematically 聪明”似乎是最快的。我不知道那个模属性,我的错。 我知道这个属性,但我也不认为它会是最快的方法,而且速度很快,所以我们今天都学到了一些东西。 请注意,$D % 9 会产生 0,而其他方法会产生 9。实际等效函数是 ($D+0) && ($D % 9 || 9) @cjm:注意到了,谢谢。我在想 split() 几乎总是比 unpack() 慢,但你的回答向我展示了另一个故事。 此外,按照您设置此基准的方式,模数得到了额外的提升,因为 $D 从字符串到整数的转换只发生一次,而不是 1,000,000 次。在一个真实的程序中,您每次都会使用不同的数字字符串。不过,它仍然会比拆分字符串快得多。

以上是关于Perl 中字符串的最快校验位例程是啥?的主要内容,如果未能解决你的问题,请参考以下文章

删除 perl 字符串中所有前导零的最优雅和最快的方法

单台机器最快的 Perl IPC/消息队列是啥?

将参数传递给 Perl 子例程时,是不是会影响数据复制性能?

Perl 中的快速字符串校验和函数生成 0..2^32-1 范围内的值

在 Oracle 中,限制字符串中字符的最快方法是啥?

Java中最快的子字符串搜索方法是啥