我有一个散列%signal_db
的散列。一个典型的元素是:$signal_db{$cycle}{$key}
。有10000个信号和10000个按键。
有没有办法优化(按时间)这段代码:
foreach my $cycle (sort numerically keys %signal_db) {
foreach my $key (sort keys %{$signal_db{$cycle}}) {
print $signal_db{$cycle}{$key}.$key."n";
}
}
元素必须按照与我的代码中相同的顺序打印。
两个微观优化:映射内部哈希而不是常量解引用,缓冲区而不是常量打印。可以使用替代存储格式来取消排序,测试了两种变体。结果:
Rate original try3 alternative alternative2
original 46.1/s -- -12% -21% -32%
try3 52.6/s 14% -- -10% -22%
alternative 58.6/s 27% 11% -- -13%
alternative2 67.5/s 46% 28% 15% --
结论:
最好使用预排序的存储格式,但如果没有C,win可能在100%以内(在我的测试数据集上)。提供的有关数据的信息表明,外部哈希中的键几乎是连续数字,所以这需要数组。
脚本:
#!/usr/bin/env perl
use strict; use warnings;
use Benchmark qw/timethese cmpthese/;
my %signal_db = map { $_ => {} } 1..1000;
%$_ = map { $_ => $_ } 'a'..'z' foreach values %signal_db;
my @signal_db = map { { cycle => $_ } } 1..1000;
$_->{'samples'} = { map { $_ => $_ } 'a'..'z' } foreach @signal_db;
my @signal_db1 = map { $_ => [] } 1..1000;
@$_ = map { $_ => $_ } 'a'..'z' foreach grep ref $_, @signal_db1;
use Sort::Key qw(nsort);
sub numerically { $a <=> $b }
my $result = cmpthese( -2, {
'original' => sub {
open my $out, '>', 'tmp.out';
foreach my $cycle (sort numerically keys %signal_db) {
foreach my $key (sort keys %{$signal_db{$cycle}}) {
print $out $signal_db{$cycle}{$key}.$key."n";
}
}
},
'try3' => sub {
open my $out, '>', 'tmp.out';
foreach my $cycle (map $signal_db{$_}, sort numerically keys %signal_db) {
my $tmp = '';
foreach my $key (sort keys %$cycle) {
$tmp .= $cycle->{$key}.$key."n";
}
print $out $tmp;
}
},
'alternative' => sub {
open my $out, '>', 'tmp.out';
foreach my $cycle (map $_->{'samples'}, @signal_db) {
my $tmp = '';
foreach my $key (sort keys %$cycle) {
$tmp .= $cycle->{$key}.$key."n";
}
print $out $tmp;
}
},
'alternative2' => sub {
open my $out, '>', 'tmp.out';
foreach my $cycle (grep ref $_, @signal_db1) {
my $tmp = '';
foreach (my $i = 0; $i < @$cycle; $i+=2) {
$tmp .= $cycle->[$i+1].$cycle->[$i]."n";
}
print $out $tmp;
}
},
} );
my %signal_db = map {$_ => {1 .. 1000}} 1 .. 1000;
sub numerically {$a <=> $b}
sub orig {
my $x;
foreach my $cycle (sort numerically keys %signal_db) {
foreach my $key (sort keys %{$signal_db{$cycle}}) {
$x += length $signal_db{$cycle}{$key}.$key."n";
}
}
}
sub faster {
my $x;
our ($cycle, $key, %hash); # move allocation out of the loop
local *hash; # and use package variables which are faster to alias into
foreach $cycle (sort {$a <=> $b} # the {$a <=> $b} literal is optimized
keys %signal_db) {
*hash = $signal_db{$cycle}; # alias into %hash
foreach $key (sort keys %hash) {
$x += length $hash{$key}.$key."n"; # simplify the lookup
}
}
}
use Benchmark 'cmpthese';
cmpthese -5 => {
orig => &orig,
faster => &faster,
};
得到:
速率原点更快原始2.56/s--15%更快3.03/s 18%--
这不是一个巨大的收获,但它是有意义的。在不更改数据结构以使用预排序数组的情况下,您无法进行更多优化。(或在XS中写入全部内容)
将foreach
循环切换为使用外部包变量可以节省一点时间,因为perl不必在循环中创建词法。此外,包变量的别名转换速度似乎要快一些。将内部查找减少到单个级别也有帮助。
我假设您正在打印到STDOUT
,然后将输出重定向到文件?如果是这样的话,使用Perl直接打开输出文件,然后打印到该句柄可以提高文件IO性能。另一个微观优化可以是用不同的记录大小进行实验。例如,在内部循环中构建一个数组,然后在外部循环的底部连接/打印它,这样可以节省时间吗?但这是相当依赖于设备的(由于其他IO缓存层,可能毫无意义),所以我将把测试留给您。
我首先尝试Sort::Key
模块,因为排序比简单的循环和打印花费更长的时间。此外,如果内部散列键(大部分)相同,那么你应该简单地对它们进行预排序,但我认为情况并非如此,否则你已经在做了。
显然,您也应该尝试将$signal_db{$cycle}分配给引用。您可能会发现each
比keys
加上检索更快,尤其是与Sort::Key
一起使用时。我会检查地图是否也比foreach
运行得更快,可能是一样的,但谁知道呢。如果你给print
传递一个列表或多次调用它,你可能会发现它运行得更快。
我还没有尝试过这个代码,但把除了each
之外的所有这些想法放在一起会得到:
foreach my $cycle (nsort keys %signal_db) {
my $r = $signal_db{$cycle};
map { print ($r->{$_},$_,"n"); } (nsort keys %$r);
}
这里有一篇关于perl中排序的文章,如果您想了解如何使用each
,请查看Schwartzian Transform。
如果您的代码不需要具有安全意识,那么您可以通过设置Perl_HASH_SSEED或相关变量来禁用Perl对算法复杂性攻击的保护,和/或通过更改设置来重新编译Perl,这样Perl的keys
和values
命令就已经按排序顺序返回了键或值,从而节省了大量的排序时间。但在这样做之前,请先看28C3的演讲。我不知道这是否可行,你需要阅读Perl源代码的这一部分,也许在C.中实现循环更容易。