【问题标题】:How can I improve Perl's performance with massive data?如何使用海量数据提高 Perl 的性能?
【发布时间】:2010-01-07 09:26:25
【问题描述】:

我正在寻找改进我的算法以解决分配大量数据的难题。 如果有人能想到一些使它运行得更快的好主意,我将不胜感激。

我有 8 个文件包含 2 个参数:

1) 起点

2) 终点

这些类型的数据在文件中以不同的数字重复。 意味着我可以拥有一个包含 200 个起点 + 200 个终点的文件,或者一个包含 50000 个起点 + 50000 个终点的文件。

好的,这些数据的可能性反映了 X 轴数据点。每个点 start/end 将被排序到 X_sort_array 中。

问题从 Y 轴数据点开始。

为了创建 Y 轴数据点,我编写了一些算法,在整个 X_sort_array 点上运行,并检查每个点是否在所有起始点之间。如果是为 Y_array[x_point] 加一个等等...

我会在这里粘贴我的大部分代码,如果有语法/方法可以更高效地执行它,我会很高兴知道。

当 sort_all_legth 大于 50000 - 慢动作开始:

    my $sort_all_length = @{$values{"$tasks_name[$i]\_sort\_allvals"}};
    my $core_count = 0;
    my $m=0;

    for my $k(0 .. $sort_all_length-1){



    if (($values{"$tasks_name[$i]\_sort\_allvals"}->[$k] eq $values{"$tasks_name[$i]\_sort\_allvals"}->[$k-1])
& ($k != 0) ) {
}

    else {
    my $pre_point_flag;
    # CHEACK PREVIUES POINT:
    for my $inst_num(0 .. $sort_all_length/2-1){
     $pre_point_flag=1;
     if ( ($values{"TID$i\_$tasks_name[$i]\_SHL"}->[$inst_num] <=    $values{"$tasks_name[$i]\_sort\_allvals"}->[$k]-1) 
   & ($values{"$tasks_name[$i]\_sort\_allvals"}->[$k]-1 <= $values{"TID$i\_$tasks_name[$i]\_EHL"}->[$inst_num]) )  {
    $pre_point_flag=0;
    last;
 }
}
if ($pre_point_flag eq 1){
 push(@{$values{"$tasks_name[$i]\_Y"}},0);
 push(@{$values{"$tasks_name[$i]\_X"}},$values{"$tasks_name[$i]\_sort\_allvals"}->[$k]-1);
}

# CHEACK DEFINE POINT:
for my $inst_num(0 .. $sort_all_length/2-1){
 if ( ($values{"TID$i\_$tasks_name[$i]\_SHL"}->[$inst_num] <= $values{"$tasks_name[$i]\_sort\_allvals"}->[$k]) 
   & ($values{"$tasks_name[$i]\_sort\_allvals"}->[$k] <= $values{"TID$i\_$tasks_name[$i]\_EHL"}->[$inst_num]) )  {
    $core_count++;
 }

}
push(@{$values{"$tasks_name[$i]\_Y"}},$core_count);
push(@{$values{"$tasks_name[$i]\_X"}},$values{"$tasks_name[$i]\_sort\_allvals"}->[$k]);

# CHEACK LATER POINT:
for my $inst_num(0 .. $sort_all_length/2-1){
 $pre_point_flag=1;
 if ( ($values{"TID$i\_$tasks_name[$i]\_SHL"}->[$inst_num] <= $values{"$tasks_name[$i]\_sort\_allvals"}->[$k]+1) 
   & ($values{"$tasks_name[$i]\_sort\_allvals"}->[$k]+1 <= $values{"TID$i\_$tasks_name[$i]\_EHL"}->[$inst_num]) )  {
    $pre_point_flag=0;
    last;
 }
}
if ($pre_point_flag eq 1){
 push(@{$values{"$tasks_name[$i]\_Y"}},0);
 push(@{$values{"$tasks_name[$i]\_X"}},$values{"$tasks_name[$i]\_sort\_allvals"}->[$k]+1);
}

if ($y_max_val < $core_count){
 $y_max_val = $core_count;
}

$core_count = 0;
$m++;
}



}

具有良好性能的小数据示例:

(IP 大小为 50000 时性能不佳!)

数据库:任务:字节位置

数据库:二进制文件大小:3216

数据库:开始/结束值的数量 = 804

数据库:计算已排序 I.P 数组中的活动核心数(大小:1608)...

数据库:IP数组值:

375127997,375135023,375177121,375177978,375225484,375226331, 375273745,375274563,375320063,375325255,375372479,375377085, 375422115,375422896,375473198,375474058,375517412,375518169, 375561967,375562760,375606301,375607092...

测试示例输入/输出:

输入是 X 轴上的起点和终点:

16,255

16,255

16,255

100,355

200,455

数据库:任务:测试

数据库:二进制文件大小:20

数据库:开始/结束值的数量 = 5

数据库:已排序的 I.P 数组(X 轴):

16,16,16,100,200,255,255,255,355,455

每个点都称为兴趣点(在 X 轴上)。

为了计算 Y 值,我用每个 I.P. 画一条垂直线

然后我计算有多少输入线(起点/终点)通过第一条垂直线。

结果是第一个 X I.P 的第一个 Y 点

数据库:计算已排序 I.P 数组中的活动核心数(大小:10)...

输出向量:

数据库:排序核心数组(Y 轴):

0,3,4,5,5,2,1,0

在我的算法中,我在每个 I.P 开始/结束块之前和之后添加了另一个点。

这样你就可以在开头/结尾看到零!

3 - 表示 X 点 16 处有 3 条切片线

4 - 表示在 X 点 100 处有 4 条切片线

5 - (1)表示在 X 点 200 处有 5 条切片线

5 - (2)表示在 X 点 255 处有 5 条切片线

2 - 表示在 X 点 355 处有 2 条切片线

1 - 表示 X 点 455 处有 1 条切片线

希望这些扩展示例能让您更了解算法。 :)

我将重建代码以提高可读性。

谢谢, 尤达。

【问题讨论】:

  • 不显示代码,而是显示您拥有的一些示例数据
  • 我很难弄清楚您要做什么。正如 ghostdog74 建议的那样,一些示例数据可能会有所帮助——输入数据和算法的期望结果。
  • 我认为这里的算法比 Perl 本身更成问题。

标签: performance perl


【解决方案1】:

提高性能的一种非常简单的方法是学习更好的编码实践。

您忽略的一个原则是:不要重复自己。

你在里面重复了很多代码,迫使 Perl 一次又一次地计算同一个表达式。一个例子是这个字符串:

"$tasks_name[$i]\_sort\_allvals"

它在那里使用了大约 10 次。这意味着您每次使用它时 perl 都会引用该数组,以防万一它发生变化,并将该字符串放在一起。可能看起来不多,但最终会加起来。

另一个例子是这样的:

$values{"$tasks_name[$i]\_sort\_allvals"}->[$k]

它也被使用了 10 次,而 $k 实际上改变了每个循环,对于每次循环运行,整个表达式的值都是相同的。在循环开始时将其存储在单个标量中会更快,然后在循环的其余部分使用该标量,因为您可以避免强制 perl 在每个循环中解析 10 次引用。

【讨论】:

  • 算法重新设计听起来更重要,但是,是的,我这样做只是为了便于阅读!
  • 同意。但有时它只是有助于给出除此之外的重要原因。 :)
  • 感谢您的评论。我会尝试更改它并保留它以供下次使用:)
【解决方案2】:

我在跟踪它时遇到了一些麻烦,但听起来您从一开始就为每个轴 X 点重复搜索一个(方便地预排序的)数据文件 X 点数组for my $var (0..whatever) { ... last if $done; }

总计Shlemiel the Painter algorithm。这可能是性能问题的根源。您应该尽量避免不需要再次执行的部分搜索。

【讨论】:

  • 不错的算法!我不太确定我是否了解与我的算法的联系。感谢您的评论:)
【解决方案3】:

我试图理解你的程序,但我的大脑有点哽咽。你能为我们提供一个示例输入,你想要做什么的一些解释,以及好的示例输出吗?从你的例子来看,我无法做出正面或反面。

你说这是一些有效的输入:

database: task: TEST

database: binary file size: 20

database: numbers of start/end values = 5

database: sorted I.P array (X axis):

16,16,16,100,200,255,255,255,355,455

database: counting active cores in sorted I.P array (size: 10)...

这是有效的输出:

database: sorted cores array (Y axis):

0,3,4,5,5,2,1,0

但我不明白。愿意解释一下您想要实现的目标吗?

【讨论】:

    【解决方案4】:

    您根本不需要此任务的时间间隔。您可以对间隔的开始和结束进行排序,然后合并这个排序的数组并计算打开的间隔数。这绝对是 O(NlogN) 算法,在几分之一秒内处理 50k 间隔。

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    my (@starts, @ends);
    
    while(<>) {
        chomp;
        my($start, $end) = split',';
        push @starts, $start;
        push @ends, $end;
    }
    
    @starts = sort { $a <=> $b } @starts;
    @ends = sort { $a <=> $b } @ends;
    
    my $y = 0;
    my ($x, @y, @x);
    while (@starts) {
        my $x = $starts[0] <= $ends[0] ? $starts[0] : $ends[0];
        while ($starts[0] == $x) {
            $y++;
            shift @starts;
            last unless @starts;
        }
        push @x, $x;
        push @y, $y;
        while ($ends[0] == $x) {
            $y--;
            shift @ends;
            last unless @ends;
        }
    }
    
    while (@ends) {
        my $x = $ends[0];
        push @x, $x;
        push @y, $y;
        while ($ends[0] == $x) {
            $y--;
            shift @ends;
            last unless @ends;
        }
    }
    
    die "Unbalanced!" if $y;
    
    $" = ',';
    print "0,@y,0\n";
    

    【讨论】:

    • 这确实是一个死问题,由我投反对票的点费舍尔 (xoid) 提出。我确信操作员很感激,但可能早就过了这个问题。
    • 哦,我明白了。无论如何,我发现这个问题很有趣。
    【解决方案5】:

    根据你在这里所说的:

    为了创建 Y 轴数据点,我编写了一些算法,在整个 X_sort_array 点上运行,并检查每个点是否在所有起始点之间。如果是为 Y_array[x_point] 加一个等等...

    假设这是您的问题,那么修改后的二分搜索算法将在 O(log n) 中运行,或者对于 n = 1_000_000 大约需要 6 步:

    sub binary_range_search {
        my ( $range, $ranges ) = @_;
        my ( $low, $high ) = ( 0, @{$ranges} - 1 );
        while ( $low <= $high ) {
    
            my $try = int( ( $low + $high ) / 2 );
    
            $low  = $try + 1, next if $ranges->[$try][1] < $range->[0];
            $high = $try - 1, next if $ranges->[$try][0] > $range->[1];
    
            return $ranges->[$try];
        }
        return;
    }
    

    在此版本中,您将查找范围 @$range 是否与 @$ranges 中的任何范围重叠。您可以对其进行修改以在所有范围内查找单个点的重叠。这就是你要找的吗?

    【讨论】:

    • 我想这可能是我一直在寻找的。我仍然需要进行一些升级才能正确使用它。我知道必须对 X_sort_array 进行排序才能使用此算法,但是 @$range 是否也需要排序?
    • 完全没有。 @$range 应该类似于 (1, 50) 或 (100, 100),在这种情况下,它将找到包含 100 的正确范围。我发布的版本只是我为了寻找重叠范围而已经拥有的东西。
    • 有没有办法计算在一个点上发生了多少 @$ranges 重叠?
    • 是的,当然。同样使用二分搜索,请参阅我的问题:stackoverflow.com/questions/2046390/… 和接受的答案:stackoverflow.com/questions/2046390/… 如果您实现 binary_range_search 迭代器版本,计算重叠数将是通过 $count++ while $brs_iterator-&gt;() 耗尽迭代器的问题。跨度>
    【解决方案6】:

    可能更好的方法是将数据加载到 BD ,在其字段上创建索引并运行 SQL SELECT start, stop FROM X WHERE Y between start, stop;

    BD 有很好的二分搜索算法。可能比你的好。

    另一种方法是将您的数据从字符串转换为二进制,并且可以使用 C 静态数组来操作它。 喜欢

    struct point {
       int start, int stop      
    }  
    
    point array[8000];
    read array form file;
    for (int i=0; i<8000; i++)
    {
    }
    

    你会想知道它有多快

    【讨论】:

    • 为什么要提一个老问题,只是说“C 更快”?
    猜你喜欢
    • 1970-01-01
    • 2021-07-03
    • 1970-01-01
    • 2011-09-22
    • 1970-01-01
    • 2011-01-01
    • 1970-01-01
    • 2015-08-16
    • 2021-07-10
    相关资源
    最近更新 更多