如何优化Perl中的二维哈希遍历?

时间:2012-01-15 16:50:31

标签: perl optimization hash multidimensional-array traversal

我有哈希哈希%signal_db。典型的元素是:$signal_db{$cycle}{$key}。有10,000个信号和10,000个密钥。

有没有办法优化(按时间)这段代码:

foreach my $cycle (sort numerically keys %signal_db) {
    foreach my $key (sort keys %{$signal_db{$cycle}}) {
        print $signal_db{$cycle}{$key}.$key."\n";
    }
}

必须按照与我的代码相同的顺序打印元素。

3 个答案:

答案 0 :(得分:7)

两个微优化:映射内部哈希而不是常量解除引用和缓冲而不是常量打印。可以使用替代存储格式摆脱排序,测试两种变体。结果:

               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;
        }
    },
} );

答案 1 :(得分:4)

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,
};

得到:

         Rate   orig faster
orig   2.56/s     --   -15%
faster 3.03/s    18%     --

不是一个巨大的收获,但它是一个东西。在不改变数据结构的情况下,您可以优化以使用预分类数组。 (或在XS中写下整件事)

切换foreach循环以使用外部包变量可节省一点时间,因为perl不必在循环中创建词法。包变量似乎也有点快了别名。将内部查找减少到单个级别也有帮助。

我假设您要打印到STDOUT然后将输出重定向到文件?如果是这样,使用Perl直接打开输出文件然后打印到该句柄可以允许改进文件IO性能。另一个微优化可能是尝试不同的记录大小。例如,它是否节省了在内部循环中构建数组的任何时间,然后在外部循环的底部连接/打印它?但这是相当依赖于设备的东西(并且由于其他IO缓存层可能毫无意义),所以我将把测试留给你。

答案 2 :(得分:3)

我首先尝试使用Sort::Key module,因为排序比简单的循环和打印需要更长的时间。此外,如果内部哈希键(大多数)相同,那么你应该简单地预先分配它们,但我会假设情况并非如此,否则你就已经这样做了。

您显然应该尝试将$ signal_db {$ cycle}分配给引用。您可能会发现eachkeys加快检索速度更快,尤其是与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 here中进行排序的文章,如果您希望了解如何使用each,请查看Schwartzian变换。

如果您的代码不需要具有安全意识,那么您可以设想通过设置algorithmic complexity attacks或相关变量和/或使用更改的设置重新编译Perl来禁用Perl对PERL_HASH_SEED的保护,以便perl的{{1 }}和keys命令已经按排序顺序返回了键或值,从而节省了大量时间对它们进行排序。但请在执行此操作之前先查看this 28C3 talk。我不知道如果这甚至可以工作,你需要阅读Perl源代码的这一部分,也许更容易在C中实现你的循环。