perl中的C类数组

use*_*481 8 arrays perl pdl

我想在内存中创建和操作(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);
    }
});
Run Code Online (Sandbox Code Playgroud)

返回:

          Rate  pdl perl
pdl   481208/s   -- -87%
perl 3640889/s 657%   --    
Run Code Online (Sandbox Code Playgroud)

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

Bor*_*din 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]
Run Code Online (Sandbox Code Playgroud)

如果这还不够,可能会在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)++; 
    },
});
Run Code Online (Sandbox Code Playgroud)

给出这些结果

          Rate  pdl  vec perl
pdl   472429/s   -- -76% -85%
vec  1993101/s 322%   -- -37%
perl 3157570/s 568%  58%   --
Run Code Online (Sandbox Code Playgroud)

所以使用vec的速度是原生数组的三分之二.大概这是可以接受的.


Dav*_*ens 7

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

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);
    }
});
Run Code Online (Sandbox Code Playgroud)

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

$ perl script.pl 1
          Rate   pdl  perl
pdl    21354/s    --  -98%
perl 1061925/s 4873%    --
Run Code Online (Sandbox Code Playgroud)

这有很多理由要弥补!但请坚持到那里.如果我们每轮进行100次迭代,我们会得到改进:

$ perl script.pl 100
        Rate  pdl perl
pdl  16906/s   -- -18%
perl 20577/s  22%   --
Run Code Online (Sandbox Code Playgroud)

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

$ perl script.pl 10000
      Rate perl  pdl
perl 221/s   -- -75%
pdl  881/s 298%   --
Run Code Online (Sandbox Code Playgroud)

对于更大的值,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]++;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

如果这一切你需要完成,我推荐使用Inline::C.

但是,如果您需要对数据执行许多操作,那么最好坚持使用PDL来实现其功能,灵活性和性能.请参阅下文,了解如何使用vec()PDL数据.


小智 5

PDL::set()PDL::get()目的更多的是作为学习辅助工具。它们构成了访问 PDL 变量的悲观方式。使用一些内置的批量访问例程会更好。PDL 构造函数本身接受 Perl 列表:

$pdl = pdl(@list)
Run Code Online (Sandbox Code Playgroud)

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

$pdl = PDL->new_from_specification(long,$elements);
$dr = $pdl->get_dataref;
$$dr = get_my_packed_string();
$pdl->upd_data;
Run Code Online (Sandbox Code Playgroud)

另请注意,您可以通过使用 PDL 对象来保存整数数组、PDL 计算(例如indadd)来“鱼与熊掌兼得”,以进行大规模数据操作,也可以vec()直接在 PDL 数据上使用您可以通过以下方法获取字符串get_dataref

vec($$dr,int(rand($N)),32);
Run Code Online (Sandbox Code Playgroud)

bswap4如果您使用的是小端系统,您将需要这些数据:

$pdl->bswap4;
$dr = $pdl->get_dataref;
vec($$dr,int(rand($N)),32)++;
$pdl->upd_data;
$pdl->bswap4;
Run Code Online (Sandbox Code Playgroud)

等等,瞧!