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 子例程时,是不是会影响数据复制性能?