当$insertPoint为101000且$records为3167时,以下Perl代码片段中的循环需要210秒才能运行。在这台机器上,等价的C代码运行一秒钟左右(见下文)。通常,当运行时出现~200倍的系数时,意味着发生了一些非常低效的事情。
知道这里可能是什么吗?
@alignedSeqs是一个字符串数组,所有字符串的长度都相同。这台机器每CPU有80G内存和8M缓存。据我所知,这段代码在内存中是“正确的”。
my $i;
my $j;
my @As=(0) x $insertPoint; # possibly faster than an array of arrays?
my @Gs=(0) x $insertPoint;
my @Cs=(0) x $insertPoint;
my @Ts=(0) x $insertPoint;
my @Is=(0) x $insertPoint;
for($i=0;$i<$records;++$i){
for($j=0; $j <$insertPoint; ++$j){
my $base=uc(substr($alignedSeqs[$i],$j,1));
if( $base eq "A"){ $As[$j]++; }
elsif($base eq "G"){ $Gs[$j]++; }
elsif($base eq "C"){ $Cs[$j]++; }
elsif($base eq "T"){ $Ts[$j]++; }
else{ $Is[$j]++; }
}
}
此更改:
my $aSeq=$alignedSeqs[$i];
for($j=0; $j <$insertPoint; ++$j){
my $base=uc(substr($aSeq,$j,1));
在运行时间上没有显著差异。
问题本质的一个线索可能是这个进程运行时在“top”中显示的内存使用情况,在这个代码开始之前,它的峰值超过12G,并且在整个部分都保持在那里。然后我修改了脚本,在这个程序中显式地释放了其他的大数据结构,它降到了10G,假设剩下的10G中的大部分都在对齐的数据中,这不是很有效的存储,因为每个字符只有一个字节,所以保存这些字符串只需要大约320M,如果Perl使用4字节unicode,则可能需要1.28G。
这是一个小的C测试程序,只够运行等效代码。很难用我的表精确计时,但“计数”部分在这样运行时大约需要1-2秒:
gcc-std=c99——踏板-墙-o测试.c
时间/测试>/dev/null
整个过程在7.1秒内完成。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h> //for toupper
// prototypes
void boom(char *string);
int main(void){
int records = 3167;
int slen = 101000;
char **aligned=NULL;
malloc(sizeof(char *)*slen);
int i,j;
unsigned int k;
aligned=malloc(sizeof(char *)*slen);
if(!aligned)boom(" Could not allocate first array");
fprintf(stderr,"DEBUG: filling with random data\n");
for(i=0;i<records;i++){
aligned[i]=malloc(sizeof(char)*(slen+1));
if(!aligned[i])boom(" Could not allocate an aligned array");
for(j=0;j<slen;j++){
k=rand();
k -=((k>>10)<<10);
switch(k){
case 0: aligned[i][j]='A'; break;
case 1: aligned[i][j]='C'; break;
case 2: aligned[i][j]='G'; break;
case 3: aligned[i][j]='T'; break;
default: aligned[i][j]='-'; break;
}
}
}
fprintf(stderr,"DEBUG: allocating space for counts\n");
int *As=calloc(sizeof(int),slen);
int *Gs=calloc(sizeof(int),slen);
int *Cs=calloc(sizeof(int),slen);
int *Ts=calloc(sizeof(int),slen);
int *Is=calloc(sizeof(int),slen);
if(!As || !Gs || !Cs || !Ts || !Is)boom(" Could not allocate memory for counts");
fprintf(stderr,"DEBUG: counting\n");
for(i=0;i<records;i++){
for(j=0; j <slen; j++){
int base=toupper(aligned[i][j]);
if( base == 'A'){ As[j]++; }
else if(base == 'G'){ Gs[j]++; }
else if(base == 'C'){ Cs[j]++; }
else if(base == 'T'){ Ts[j]++; }
else{ Is[j]++; }
}
}
fprintf(stderr,"DEBUG: emitting\n");
for(j=0;j<slen;j++){
fprintf(stdout,"%5d %4d %4d %4d %4d %4d\n",j,As[j],Gs[j],Cs[j],Ts[j],Is[j]);
}
}
void boom(char *string){
printf("Fatal error: %s\n",string);
exit(EXIT_FAILURE);
}
最佳答案
我误解了你的问题。下面是另一个解决这个问题的尝试:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use Time::HiRes qw{ time };
my $insertPoint = 100000;
my @alignedSeqs;
push @alignedSeqs, join '', map qw(A C G T X)[rand 5], 1 .. $insertPoint for 1 .. 3000;
my $start = time();
my %count;
for my $i (0 .. $#alignedSeqs) {
my $j = 0;
for my $ch (split //, $alignedSeqs[$i]) {
++$count{$ch}[$j++]
}
}
for my $ch (keys %count) {
next if $ch =~ /[ACTGI]/;
$count{I}[$_] += $count{$ch}[$_] for 0 .. $insertPoint;
delete $count{$ch};
}
my $end = time();
print Dumper \%count;
print $count{A}[0] + $count{C}[0] + $count{G}[0] + $count{T}[0] + $count{I}[0], "\n";
print $end - $start, " seconds\n";
或者,稍微快一点(90秒对60秒),使用substr引用:
my $s = $alignedSeqs[0];
my @p = map \ substr($s, $_, 1), 0 .. $insertPoint - 1;
for my $i (0 .. $#alignedSeqs) {
$s = $alignedSeqs[$i];
my $j = 0;
for my $ch (@p) {
++$count{$$ch}[$j++]
}
}
for my $ch (keys %count) {
next if $ch =~ /[ACTGI]/;
$count{I}[$_] += $count{$ch}[$_] for 0 .. $insertPoint - 1;
delete $count{$ch};
}
关于c - 为什么这个Perl遍历字符串然后字符那么慢?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27554209/