8

我想在内存中创建和操作大型(4 字节)整数数组。大体上,我的意思是大约数亿。阵列中的每个单元格将充当染色体上位置的计数器。我所需要的只是让它适合内存,并且可以快速(O(1))访问元素。我计算的东西不是稀疏特征,所以我不能使用稀疏数组。

我不能用常规的 perl 列表来做到这一点,因为 perl(至少在我的机器上)每个元素使用 64 个字节,所以我使用的大多数生物的基因组都太大了。我尝试通过 SQLite 和哈希绑定将数据存储在磁盘上,尽管它们可以工作,但速度非常慢,尤其是在普通驱动器上。(当我在 4 驱动器 raid 0 上运行时,它工作得相当好)。

我以为我可以使用 PDL 数组,b/c PDL 像 C 一样存储它的数组,每个元素只使用 4 个字节。但是,我发现与 perl 列表相比,更新速度非常慢:

use PDL;
use Benchmark qw/cmpthese/;

my $N = 1_000_000;
my @perl = (0 .. $N - 1);
my $pdl = zeroes $N;

cmpthese(-1,{ 
    perl => sub{
        $perl[int(rand($N))]++;
    },
    pdl => sub{
        # note that I'm not even incrementing here just setting to 1
        $pdl->set(int(rand($N)), 1);
    }
});

回报:

          Rate  pdl perl
pdl   481208/s   -- -87%
perl 3640889/s 657%   --    

有谁知道如何提高 pdl set() 性能,或者知道可以实现此目的的不同模块?

4

7 回答 7

8

我不知道您将获得什么样的性能,但我建议使用此处vec记录的函数将字符串拆分为位字段。我进行了实验,发现我的 Perl 可以容忍一个字符串最长为字符。这对应于 125,000,000 个 32 位值。500_000_000

my $data = "\0" x 500_000_000;
vec($data, 0, 32)++;            # Increment data[0]
vec($data, 100_000_000, 32)++;  # Increment data[100_000_000]

如果这还不够,那么 Perl 的构建中可能有一些东西可以控制限制。或者,如果您认为您可以获得更小的字段 - 例如 16 位计数 -vec将接受 2 到 32 的任何幂的字段宽度。

编辑:我相信字符串大小限制与 32 位 Windows 进程上的 2GB 最大私有工作集有关。如果您正在运行 Linux 或拥有 64 位 perl,您可能比我幸运。


我已经像这样添加到您的基准程序中

my $vec = "\0" x ($N * 4);

cmpthese(-3,{ 
    perl => sub{
        $perl[int(rand($N))]++;
    },
    pdl => sub{
        # note that I'm not even incrementing here just setting to 1
        $pdl->set(int(rand($N)), 1);
    },
    vec => sub {
        vec($vec, int(rand($N)), 32)++; 
    },
});

给出这些结果

          Rate  pdl  vec perl
pdl   472429/s   -- -76% -85%
vec  1993101/s 322%   -- -37%
perl 3157570/s 568%  58%   --

所以使用vec速度是原生数组的三分之二。想必这是可以接受的。

于 2012-03-16T02:05:14.507 回答
7

您想要的 PDL 命令是indadd. (感谢 PDL Pumpking 的 Chris Marshall 在其他地方向我指出这一点。)

PDL 专为我所说的“矢量化”操作而设计。与 C 操作相比,Perl 操作相当慢,因此您希望将 PDL 方法调用的次数保持在最低限度,并且每次调用都做大量工作。例如,此基准测试允许您指定一次执行的更新次数(作为命令行参数)。perl 端必须循环,但 PDL 端只执行五个左右的函数调用:

use PDL;
use Benchmark qw/cmpthese/;

my $updates_per_round = shift || 1;

my $N = 1_000_000;
my @perl = (0 .. $N - 1);
my $pdl = zeroes $N;

cmpthese(-1,{ 
    perl => sub{
        $perl[int(rand($N))]++ for (1..$updates_per_round);
    },
    pdl => sub{
        my $to_update = long(random($updates_per_round) * $N);
        indadd(1,$to_update,$pdl);
    }
});

当我使用参数 1 运行它时,我得到的性能甚至比使用 时更差set,这是我所期望的:

$ perl script.pl 1
          Rate   pdl  perl
pdl    21354/s    --  -98%
perl 1061925/s 4873%    --

这是一个很大的基础来弥补!但坚持在那里。如果我们每轮进行 100 次迭代,我们会得到改进:

$ perl script.pl 100
        Rate  pdl perl
pdl  16906/s   -- -18%
perl 20577/s  22%   --

每轮更新 10,000 次,PDL 的性能比 Perl 高出四倍:

$ perl script.pl 10000
      Rate perl  pdl
perl 221/s   -- -75%
pdl  881/s 298%   --

对于更大的值,PDL 的执行速度仍然比普通 Perl 快大约 4 倍。

请注意,对于更复杂的操作,PDL 的性能可能会降低。这是因为 PDL 将为中间操作分配和拆除大型但临时的工作空间。在这种情况下,您可能需要考虑使用Inline::Pdlpp. 然而,这不是初学者的工具,所以在你确定它真的是最适合你的东西之前不要跳到那里。

所有这一切的另一种选择是Inline::C像这样使用:

use PDL;
use Benchmark qw/cmpthese/;

my $updates_per_round = shift || 1;

my $N = 1_000_000;
my @perl = (0 .. $N - 1);
my $pdl = zeroes $N;
my $inline = pack "d*", @perl;
my $max_PDL_per_round = 5_000;

use Inline 'C';

cmpthese(-1,{ 
    perl => sub{
        $perl[int(rand($N))]++ for (1..$updates_per_round);
    },
    pdl => sub{
        my $to_update = long(random($updates_per_round) * $N);
        indadd(1,$to_update,$pdl);
    },
    inline => sub{
        do_inline($inline, $updates_per_round, $N);
    },
});


__END__

__C__

void do_inline(char * packed_data, int N_updates, int N_data) {
    double * actual_data = (double *) packed_data;
    int i;
    for (i = 0; i < N_updates; i++) {
        int index = rand() % N_data;
        actual_data[index]++;
    }
}

对我来说,内联函数始终胜过 Perl 和 PDL。对于较大的值$updates_per_round,比如 1,000,我得到Inline::C的版本比纯 Perl 快大约 5 倍,比 PDL 快 1.2 到 2 倍。即使$updates_per_round只有 1,Perl 轻松击败 PDL,内联代码也比 Perl 代码快 2.5 倍。

如果这就是您需要完成的全部,我建议使用Inline::C.

但是,如果您需要对数据执行许多操作,则最好坚持使用 PDL,因为它具有强大的功能、灵活性和性能。请参阅下文,了解如何使用vec()PDL 数据。

于 2012-03-22T17:02:26.617 回答
4

PDL::set()并且PDL::get()更多的是作为学习辅助工具而不是其他任何东西。它们构成了访问 PDL 变量的最简单方法。使用一些内置的批量访问例程会好得多。PDL 构造函数本身接受 Perl 列表:

$pdl = pdl(@list)

并且相当快。您还可以使用 直接从 ASCII 文件加载数据PDL::rcols,或使用许多 IO 例程之一从二进制文件加载数据。如果您将数据作为机器顺序打包字符串,您甚至可以直接访问 PDL 内存:

$pdl = PDL->new_from_specification(long,$elements);
$dr = $pdl->get_dataref;
$$dr = get_my_packed_string();
$pdl->upd_data;

另请注意,您可以通过使用 PDL 对象来保存您的整数数组、PDL 计算indaddvec()如您可以通过以下get_dataref方法获取的字符串:

vec($$dr,int(rand($N)),32);

bswap4如果您使用的是 little-endian 系统,您将需要这些数据:

$pdl->bswap4;
$dr = $pdl->get_dataref;
vec($$dr,int(rand($N)),32)++;
$pdl->upd_data;
$pdl->bswap4;

等等,瞧!

于 2012-04-18T14:19:25.620 回答
2

当操作可以线程化时,PDL 获胜,显然它没有针对随机访问和分配进行优化。也许有更多 PDL 知识的人可以提供帮助。

于 2012-03-16T01:40:57.140 回答
2

CPAN 上的Packed::Array可能会有所帮助。

Packed::Array 提供了一个压缩的有符号整数数组类。使用 Packed::Array 构建的数组只能保存与您的平台原生整数匹配的有符号整数,但只占用保存这些整数实际所需的内存。因此,对于 32 位系统,不是每个数组条目占用大约 20 个字节,而是只占用 4 个字节。

于 2012-03-16T04:52:46.053 回答
2

因为使用的是整数,应该可以与染色体一起使用试试这个

use PDL;
use Benchmark qw/cmpthese/;

my $N =  1_000_000;
my @perl;
@perl = (0 .. $N - 1);
my $pdl;
$pdl = (zeroes($N));

cmpthese(-1,{ 
perl => sub{
    $perl[int(rand($N))]++;
},
pdl2 => sub{
    # note that I'm not even incrementing here just setting to 1
    $pdl->set(int(rand($N)), 1);
    $pdl2 = pack "w*", $pdl;
}
});

我从中得到的输出是......

           Rate  pdl2  perl
pdl2   46993/s    --  -97%
perl 1641607/s 3393%    --

这与我第一次尝试这段代码时没有增加我得到的 2 美分时的性能差异很大

          Rate  pdl perl
pdl   201972/s   -- -86%
perl 1472123/s 629%   -- 
于 2012-04-18T17:29:32.533 回答
0

我的上述答案可能毫无价值......这可能会有所帮助......

 use PDL;
$x = sequence(45000,45000);

现在除非你有 16 GB 的内存并使用

$PDL::BIGPDL=1;
于 2013-05-12T05:28:53.127 回答