当$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/

10-09 15:53