当$ insertPoint为101000且$ records为3167时,以下Perl代码段中的循环运行需要210秒。等效C代码在此机器上运行一秒左右(见下文)。通常当出现运行时间大约200倍的因素时,这意味着正在发生非常低效的事情。
知道它可能在这里吗?
@alignedSeqs是一个字符串数组,长度相同。该机器具有80G的RAM和每个CPU 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));
在运行时间方面没有显着差异。
问题性质的一个线索可能是此过程运行时“顶部”显示的内存使用情况,该代码在此代码启动之前达到峰值12G以上,并在本节中保留。然后我更改了脚本以显式释放此程序中的其他大型数据结构,并降至10G。假设剩下的10G中的大部分都在对齐数据中,而不是非常有效的存储,因为每个字符只有一个字节,它只需要大约320M来保存这些字符串,或者如果Perl使用4字节unicode则可能需要1.28G。
这是一个小型的C测试程序,足以运用相同的代码。很难用我的手表准确计时,但“计数”部分在这样运行时需要1-2秒:
gcc -std = c99 --pedantic -Wall -o test test.c 时间./test&gt; / dev / null
整个事情在7.1s完成。
#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);
}
答案 0 :(得分:2)
我误解了你的问题。这是解决它的另一种尝试:
#!/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};
}
答案 1 :(得分:1)
这my @As=(0) x $insertPoint;
可能是浪费时间和记忆。使用一堆标量(每个标量分配内存)预加载数组效率不高。您可能正在寻找的是my @As; $#As = $insertPoint + 1
,它只会在不填充数组的情况下增长。但是Perl在增长阵列方面是有效的。此步骤是不必要的,并且会导致问题,因为数组不会返回其真实长度。
至于循环,我会尝试使用数组散列来消除if / else链。并且要使用循环迭代器而不是手动循环,Perl可以更好地优化它们。
for my $i (0..$records-1) {
for my $j (0..$insertPoint-1) {
my $base = uc(substr($alignedSeqs[$i],$j,1));
$Bases{$base}[$j]++;
}
}
您可能希望将原始数据问题作为另一个问题发布,并获得一些新方法。
最后,您要找出代码花费时间的唯一方法是使用Devel::NYTProf之类的分析器。而且,最后,处理整数和逐个字符是C真正快速的事情,并且很难被击败。