Perl快速矩阵乘法

时间:2013-05-14 20:09:25

标签: performance perl statistics fastcgi matrix-multiplication

我在perl http://en.wikipedia.org/wiki/Fisher_information中实现了以下统计计算 结果是正确的。我知道这是因为我有100个与输入和输出匹配的测试用例。问题是我每次运行脚本时都需要多次计算。对此函数的平均调用次数约为530.我使用Devel :: NYTProf来查找这个函数以及缓慢部分的位置。我已经优化了算法,只能遍历矩阵的上半部分,并将它反映到底部,因为它们是相同的。我不是perl专家,但我需要知道是否有什么我可以尝试加速perl。此脚本将分发给客户端,因此无法编译C文件。我可以尝试另一个perl库吗?如果可能的话,这需要速度低于秒。

更多信息是$ MatrixRef是一个浮点数矩阵,$ rows by $ variables。这是函数的NYTProf转储。

#-----------------------------------------------
#
#-----------------------------------------------
sub ComputeXpX
# spent 4.27s within ComputeXpX which was called 526 times, avg 8.13ms/call:
# 526 times (4.27s+0s) by ComputeEfficiency at line 7121, avg 8.13ms/call
{
526 0s                  my ($MatrixRef, $rows, $variables) = @_;

526 0s                  my $r = 0;
526 0s                  my $c = 0;
526 0s                  my $k = 0;
526 0s                  my $sum = 0;
526 0s                  my @xpx = ();

526 11.0ms              for ($r = 0; $r < $variables; $r++)
                        {
14202   19.0ms                my @temp = (0) x $variables;
14202   6.01ms                push(@xpx, \@temp);
526 0s                  }
526 7.01ms              for ($r = 0; $r < $variables; $r++)
                        {
14202   144ms                   for ($c = $r; $c < $variables; $c++)
                                {
198828  43.0ms                                  $sum = 0;
                                        #for ($k = 0; $k < $rows; $k++)
198828  101ms                           foreach my $RowRef (@{$MatrixRef})
                                        {
                                                #$sum += $MatrixRef->[$k]->[$r]*$MatrixRef->[$k]->[$c];
6362496 3.77s                                   $sum += $RowRef->[$r]*$RowRef->[$c];
                                        }

198828  80.1ms                                  $xpx[$r]->[$c] = $sum;
                                                #reflect on other side of matrix
198828  82.1ms                                  $xpx[$c]->[$r] = $sum if ($r != $c);
14202   1.00ms                          }
526 2.00ms                  }

526 2.00ms                  return \@xpx;
}

2 个答案:

答案 0 :(得分:2)

由于结果矩阵的每个元素都可以独立计算,因此应该可以并行计算它们中的一些/全部。换句话说,最内层循环的实例都不依赖于任何其他循环的结果,因此它们可以在自己的线程上同时运行。

答案 1 :(得分:2)

你真的没有太多可以做的,没有用C语言重写部分,或者转向更好的数学运算框架而不是裸机Perl(→PDL!)。

一些小的优化想法:

  • 使用包含零的arrayrefs初始化@xpx。这是不必要的,因为您为每个位置分配一个值。如果要预先分配数组空间,请分配$#array值:

    my @array;
    $#array = 100; # preallocate space for 101 scalars
    

    这通常不是很有用,但您可以使用和不使用基准测试。

  • 迭代范围;不要使用C风格的for循环:

    for my $c ($r .. $variables - 1) { ... }
    

    Perl标量对于数学运算来说不是很快,因此将范围迭代卸载到较低级别获得加速。

  • 尝试更改循环的顺序,并通过缓存一定级别的数组访问来玩具。在标量中保留$my $xpx_r = $xpx[$r]将减少数组访问次数。如果您的输入足够大,则转换为速度增益。请注意,这仅在缓存值为引用时才有效。

请记住,perl只进行了很少的“大”优化,并且编译生成的操作码树非常类似于源代码。


编辑:在线程

Perl线程是重量级的野兽,实际上克隆了当前的解释器。这非常像分叉。

跨越线程边界共享数据结构是可能的(use threads::shared; my $variable :shared = "foo"),但存在各种缺陷。在Thread::Queue中传递数据更为清晰。

通过多个线程拆分一个产品的计算可能会导致您的线程执行的通信多于计算。您可以对在线程之间划分某些行的责任的解决方案进行基准测试。但我认为这里很难有效地重新组合解决方案。

更有可能是从一开始就运行一堆工作线程。所有线程都侦听包含一对矩阵和返回队列的队列。然后,工作人员会将问题出列,然后发送回解决方案。可以并行运行多个计算,但单个矩阵乘法将更慢。您的其他代码必须进行重构才能利用并行性。

未经测试的代码:

use strict; use warnings; use threads; use Thread::Queue;

# spawn worker threads:
my $problem_queue = Thread::Queue->new;
my @threads = map threads->new(\&worker, $problem_queue), 1..3; # make 3 workers

# automatically close threads when program exits
END {
  $problem_queue->enqueue((undef) x @threads);
  $_->join for @threads;
}

# This is the wrapper around the threading,
# and can be called exactly as ComputeXpX
sub async_XpX {
  my $return_queue = Thread::Queue->new();
  $problem_queue->enqueue([$return_queue, @_]);
  return sub { $return_queue->dequeue };
}

# The main loop of worker threads
sub worker {
  my ($queue) = @_;
  while(defined(my $problem = $queue->dequeue)) {
     my ($return, @args) = @$problem;
     $return->enqueue(ComputeXpX(@args));
  }
}

sub ComputeXpX { ... } # as before

async_XpX返回一个最终会收集计算结果的coderef。这允许我们继续使用其他东西,直到我们需要结果。

# start two calculations
my $future1 = async_XpX(...);
my $future2 = async_XpX(...);

...; # do something else

# collect the results
my $result1 = $future1->();
my $result2 = $future2->();

我在没有进行实际计算的情况下对裸机线程代码进行基准测试,并且通信与计算一样昂贵。即运气好的话,你可能会开始在至少有四个处理器/内核线程的机器上获益。

关于剖析线程代码的说明:我知道无法优雅地执行此操作。对线程化代码进行基准测试,但使用单线程测试用例进行分析可能更为可取。