使用共享变量的 Perl 线程性能



我正在做一个用Perl实现的项目,并认为使用线程来分发工作是一个想法,因为任务可以彼此独立完成,并且只能从内存中的共享数据中读取。但是,性能远不及我的预期。因此,经过一些调查,我只能得出结论,Perl 中的线程基本上很糟糕,但我一直想知道一旦我实现一个共享变量,性能就会下降。

例如,这个小程序没有任何共享内容,并且消耗了 75% 的 CPU(正如预期的那样(:

use threads;
sub fib {
  my ( $n ) = @_;
  if ( $n < 2 ) {
     return $n;
  } else {
     return fib( $n - 1 ) + fib( $n - 2 );
  }
}
my $thr1 = threads->create( 'fib', 35 );
my $thr2 = threads->create( 'fib', 35 );
my $thr3 = threads->create( 'fib', 35 );
$thr1->join;
$thr2->join;
$thr3->join;

一旦我引入了一个共享变量$a,CPU 使用率就在 40% 到 50% 之间:

use threads;
use threads::shared;
my $a : shared;
$a = 1000;
sub fib {
  my ( $n ) = @_;
  if ( $n < 2 ) {
    return $n;
  } else {
    return $a + fib( $n - 1 ) + fib( $n - 2 ); # <-- $a was added here
  }
}
my $thr1 = threads->create( 'fib', 35 );
my $thr2 = threads->create( 'fib', 35 );
my $thr3 = threads->create( 'fib', 35 );
$thr1->join;
$thr2->join;
$thr3->join;

因此$a是只读的,不会发生锁定,但性能会降低。我很好奇为什么会这样。

目前,我在Windows XP上使用Cygwin下的Perl 5.10.1。不幸的是,我无法在具有(希望(更新的Perl的非Windows机器上对此进行测试。

您的代码是围绕同步结构的紧密循环。通过让每个线程将共享变量(每个线程仅复制一次(到非共享变量中来优化它。

在Perl中可以构造一个包含大量数据的共享对象,而不必担心额外的副本。生成工作线程时对性能没有影响,因为共享数据驻留在单独的线程或进程中,具体取决于是否使用线程。

use MCE::Hobo;    # use threads okay or parallel module of your choice
use MCE::Shared;
# The module option constructs the object under the shared-manager.
# There's no trace of data inside the main process. The construction
# returns a shared reference containing an id and class name.
my $data = MCE::Shared->share( { module => 'My::Data' } );
my $b;
sub fib {
  my ( $n ) = @_;
  if ( $n < 2 ) {
    return $n;
  } else {
    return $b + fib( $n - 1 ) + fib( $n - 2 );
  }
}
my @thrs;
push @thrs, MCE::Hobo->create( sub { $b = $data->get_keys(1000), fib(35) } );
push @thrs, MCE::Hobo->create( sub { $b = $data->get_keys(2000), fib(35) } );
push @thrs, MCE::Hobo->create( sub { $b = $data->get_keys(3000), fib(35) } );
$_->join() for @thrs;
exit;
# Populate $self with data. When shared, the data resides under the
# shared-manager thread (via threads->create) or process (via fork).
package My::Data;
sub new {
  my $class = shift;
  my %self;
  %self = map { $_ => $_ } 1000 .. 5000;
  bless %self, $class;
}
# Add any getter methods to suit the application. Supporting multiple
# keys helps reduce the number of trips via IPC. Serialization is
# handled automatically if getter method were to return a hash ref.
# MCE::Shared will use Serial::{Encode,Decode} if available - faster.
sub get_keys {
  my $self = shift;
  if ( wantarray ) {
    return map { $_ => $self->{$_} } @_;
  } else {
    return $self->{$_[0]};
  }
}
1;

最新更新