我想在内存中创建和操作(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()性能,或者知道可以实现这一目标的不同模块?
我不知道你将获得什么样的性能,但我建议使用此处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的速度是原生数组的三分之二.大概这是可以接受的.
您想要的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)
等等,瞧!