【问题标题】:Perl script execution keeps getting killed - running out of memoryPerl 脚本执行不断被杀死 - 内存不足
【发布时间】:2013-04-20 04:02:54
【问题描述】:

我正在尝试执行一个 perl 脚本,该脚本处理一个小的 12 x 2 文本文件(大约 260 字节)和一个大的 .bedgraph 文件(至少 1.3 MB 大小)。从这两个文件中,脚本会输出一个新的床位图文件。

我已经在其他 3 个 .bedgraph 文件上运行了这个脚本,但我尝试在其余的文件上运行它,进程不断收到 Killed

perl 脚本在每个 .bedgraph 文件上运行平均需要大约 20 分钟。

我在本地机器上运行 perl 脚本(不是从服务器上)。我使用的是 Linux OS Ubuntu 12.04 系统 64 位 4GB RAM。

为什么我的 perl 脚本执行总是被终止,我该如何解决这个问题?

这是脚本:

# input file handle
open(my $sizes_fh, '<', 'S_lycopersicum_chromosomes.size') or die $!;

# output file handles
open(my $output, '+>', 'tendaysafterbreaker_output.bedgraph') or die $!;

my @array;

while(<$sizes_fh>){
    chomp;
    my ($chrom1, $size) = split(/\t/, $_);
    @array = (0) x $size;

    open(my $bedgraph_fh, '<', 'Solanum_lycopersicum_tendaysafterbreaker.bedgraph') or die $!;
    while(<$bedgraph_fh>){
        chomp;
        my ($chrom2, $start, $end, $FPKM) = split(/\t/, $_);

        if ($chrom1 eq $chrom2){
            for(my $i = $start; $i < $end; $i++){
                $array[$i] += $FPKM;
            }
        }
    }

    close $bedgraph_fh or warn $!;

    my ($last_start, $last_end) = 0;
    my $last_value = $array[0];

    for (my $i = 1; $i < $#array; $i++){
        my $curr_val = $array[$i];
        my $curr_pos = $i;

        # if the current value is not equal to the last value
        if ($curr_val != $last_value){
            my $last_value = $curr_val;
            print $output "$chrom1\t$last_start\t$last_end\t$last_value\n";
            $last_start = $last_end = $curr_pos;
        } else {
            $last_end = $i;
        }
    }
}

close $sizes_fh or warn $!;

【问题讨论】:

  • 某事正在向进程发送SIGKILL,可能是内核的OOM 杀手。 dmesg 会告诉你是否是这种情况。
  • dmesg 输出一堆数字,然后在底部显示Out of memory: Kill process 29571 (perl) score 555 or sacriface childKilled process 29571 (perl) ....
  • @cooldood3490,很明显您的脚本内存不足。请发布您的代码和文件示例,以便我们评估如何解决此问题。
  • @cooldood3490,上面提到的每个文件有多大? @array 失败时包含多少个元素?
  • @dan1111 .size 文件只有几个 KB,但 .bedgraph 输入文件句柄的大小至少为 1.6 MB。我认为@array 包含的元素最多约为4M。最小的是1M左右。

标签: linux perl memory process kill


【解决方案1】:

您正在尝试分配一个包含 90,000,000 个元素的数组。 Perl,由于其灵活的类型和其他高级变量特性,为此使用的内存比您预期的要多。

在我的 (Windows 7) 机器上,一个只分配这样一个数组而不做其他任何事情的程序会占用 3.5 GB 的 RAM。

有多种方法可以避免这种巨大的内存使用。以下是一对:

The PDL module 用于科学数据处理,旨在有效地将巨大的数字数组存储在内存中。不过,这将改变分配和使用数组的语法(并且它以各种其他方式与 Perl 的语法混淆)。

DBM::Deep 是一个在文件中分配数据库的模块——然后让您通过普通数组或散列访问该数据库:

use DBM::Deep;
my @array;
my $db = tie @array, "DBM::Deep", "array.db";

#Now you can use @array like a normal array, but it will be stored in a database.

【讨论】:

  • 我没用过这个模块。应用上述更改,我收到错误 DBM::Deep: File type mismatch at...
  • @cooldood3490,抱歉,出错了。上面修改过的代码已经过测试并且对我有用。
  • 它似乎工作了(进程不再被杀死,我的电脑也没有因为更多的内存被吃掉而滞后)但现在需要更长的时间。我为其中一个 .bedgraph 文件运行了一个多小时的脚本,直到我停止了它。我现在重新运行它。这次我要让它运行多久就运行多久。你知道使用DBM::Deep 是否会减慢整个脚本的执行时间吗?
  • @cooldood3490,是的,这将使它显着变慢,因为它现在必须从文件中读取和写入。 PDL 解决方案可能值得尝试,因为它应该允许数组适合内存(除非您的内存限制非常低)。这样会更快。与您的原始脚本相比,它甚至可能会加快执行速度。
【解决方案2】:

如果您了解一点 C 语言,那么将数组操作转移到低级代码中是非常简单的。使用 C 数组占用的空间更少,而且速度更快。但是,您会丢失诸如边界检查之类的好东西。这是 Inline::C: 的实现:

use Inline 'C';
...;
__END__
__C__
// note: I don't know if your data contains only ints or doubles. Adjust types as needed
int array_len = -1; // last index
int *array = NULL;

void make_array(int size) {
  free(array);
  // if this fails, start checking return value of malloc for != NULL
  array = (int*) malloc(sizeof(int) * size);
  array_len = size - 1;
}

// returns false on bounds error
int array_increment(int start, int end, int fpkm) {
  if ((end - 1) > array_len) return 0;
  int i;
  for (i = start; i < end; i++) {
    array[i] += fpkm;
  }
  return 1;
}

// please check if this is actually equivalent to your code.
// I removed some unneccessary-looking variables.
void loop_over_array(char* chrom1) {
  int
    i,
    last_start = 0,
    last_end   = 0,
    last_value = array[0];
  for(i = 1; i < array_len; i++) { // are you sure not `i <= array_len`?
    if (array[i] != last_value) {
      last_value = array[i];
      // I don't know how to use Perl filehandles from C,
      // so just redirect the output on the command line
      printf("%s\t%d\t%d\t%d\n", chrom1, last_start, last_end, last_value);
      last_start = i;
    }
    last_end = i;
  }
}

void free_array {
  free(array);
}

最少的测试代码:

use Test::More;

make_array(15);
ok !array_increment(0, 16, 2);
make_array(95_000_000);
ok array_increment(0, 3, 1);
ok array_increment(2, 95_000_000, 1);
loop_over_array("chrom");
free_array();
done_testing;

这个测试用例的输出是

chrom   0       1       2
chrom   2       2       1

(删除了测试输出)。编译可能需要一秒钟,但之后应该很快。

【讨论】:

    【解决方案3】:

    在从$bedgraph_fh 读取的记录中,$start 的典型值是多少?尽管哈希每个条目的开销比数组多,但如果@array 以大量未使用的条目开头,则可以节省一些内存。例如,如果您有一个包含 9000 万个元素的 @array,但前 8000 万个元素从未使用过,那么很有可能使用哈希会更好。

    除此之外,我没有看到任何明显的情况下此代码保留了它所实现的算法不需要的数据,尽管根据您的实际目标,可能存在替代算法不需要在内存中保存太多数据。

    如果您确实需要处理一组 9000 万个活动数据元素,那么您的主要选择将是购买大量 RAM 或使用某种形式的数据库。在后一种情况下,为了简单和轻量,我会选择 SQLite(通过 DBD::SQLite),但是 YMMV。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 2021-11-06
      • 1970-01-01
      • 1970-01-01
      • 2021-05-02
      相关资源
      最近更新 更多