2

我有一个大约 100,000 个元素的字符串数组。我需要遍历每个元素并用其他词替换一些词。在纯 perl 中这需要几秒钟。我需要尽可能加快速度。我正在使用以下代码段进行测试:

use strict;

my $string = "This is some string. Its only purpose is for testing.";
for( my $i = 1; $i < 100000; $i++ ) {
  $string =~ s/old1/new1/ig;
  $string =~ s/old2/new2/ig;
  $string =~ s/old3/new3/ig;
  $string =~ s/old4/new4/ig;
  $string =~ s/old5/new5/ig;
}

我知道这实际上并没有替换测试字符串中的任何内容,但它仅用于速度测试。

我寄希望于Inline::C。我以前从未使用过,Inline::C但在阅读了一点之后,我认为它实现起来相当简单。但显然,即使调用一个什么都不做的存根函数也会慢很多。这是我测试的片段:

use strict;
use Benchmark qw ( timethese );

use Inline 'C';

timethese(
   5,
   {
      "Pure Perl"  => \&pure_perl,
      "Inline C"   => \&inline_c
   }
);

sub pure_perl {
  my $string = "This is some string. Its only purpose is for testing.";
  for( my $i = 1; $i < 1000000; $i++ ) {
    $string =~ s/old1/new1/ig;
    $string =~ s/old2/new2/ig;
    $string =~ s/old3/new3/ig;
    $string =~ s/old4/new4/ig;
    $string =~ s/old5/new5/ig;
  }
}

sub inline_c {
  my $string = "This is some string. Its only purpose is for testing.";
  for( my $i = 1; $i < 1000000; $i++ ) {
    $string = findreplace( $string, "old1", "new1" );
    $string = findreplace( $string, "old2", "new2" );
    $string = findreplace( $string, "old3", "new3" );
    $string = findreplace( $string, "old4", "new4" );
    $string = findreplace( $string, "old5", "new5" );
  }
}

__DATA__
__C__

char *
findreplace( char *text, char *what, char *with ) {

  return text;

}

在我的 Linux 机器上,结果是:

Benchmark: timing 5 iterations of Inline C, Pure Perl...
  Inline C:  6 wallclock secs ( 5.51 usr +  0.02 sys =  5.53 CPU) @  0.90/s (n=5)
  Pure Perl:  2 wallclock secs ( 2.51 usr +  0.00 sys =  2.51 CPU) @  1.99/s (n=5)

纯 Perl 的速度是调用空 C 函数的两倍。完全不是我所期望的!同样,我以前从未使用过 Inline::C,所以也许我在这里遗漏了一些东西?

4

1 回答 1

6

在使用 的版本中Inline::C,您保留了原始纯 Perl 脚本中的所有内容,并且只更改了一件事:此外,您已经用s///更糟糕的实现替换了 Perl 的高度优化。调用您的虚拟函数实际上涉及工作,而s///在这种情况下,任何调用都没有做太多事情。版本运行得更快是先验的。Inline::C

在 C 端,函数

char *
findreplace( char *text, char *what, char *with ) {

  return text;

}

不是“什么都不做”的功能。调用它涉及解包参数。必须将 指向的字符串text复制到返回值。每次调用都会产生一些开销。

鉴于s///它没有替代品,因此不涉及复制。此外,Perls///是高度优化的。你确定你可以写一个更好的查找和替换来弥补调用外部函数的开销吗?

如果您使用以下实现,您应该获得相当的速度:

sub inline_c {
  my $string = "This is some string. It's only purpose is for testing.";
  for( my $i = 1; $i < 1000000; $i++ ) {
    findreplace( $string );
    findreplace( $string );
    findreplace( $string );
    findreplace( $string );
    findreplace( $string );
  }
}

__END__
__C__

void findreplace( char *text ) {
    return;

}
基准测试:对 Inline C、Pure Perl 的 5 次迭代计时...
  内联 C:6 挂钟秒(5.69 usr + 0.00 sys = 5.69 CPU)@ 0.88/s (n=5)
 纯 Perl:6 挂钟秒(5.70 usr + 0.00 sys = 5.70 CPU)@ 0.88/s (n=5)

获得速度的一种可能性是利用搜索模式和替换中涉及的任何特殊结构并编写一些东西来实现它。

在 Perl 方面,您至少应该预编译模式。

此外,由于您的问题是令人尴尬的并行,因此您最好考虑将工作分成尽可能多的块,因为您有内核可以使用。

例如,看看Benchmarks Game的regex-redux任务中的 Perl 条目:

Perl #4(仅限 fork):14.13 秒

Perl #3(分叉和线程):14.47 秒

相对

Perl #1:34.01 秒

也就是说,对并行化可能性的一些原始利用会导致 60% 的加速。这个问题不完全可比,因为替换必须按顺序完成,但仍然给你一个想法。

如果您有八个核心,请将工作分配给八个核心。

另外,请考虑以下脚本:

#!/usr/bin/env perl

use warnings;
use strict;

use Data::Fake::Text;
use List::Util qw( sum );
use Time::HiRes qw( time );

use constant INPUT_SIZE => $ARGV[0] // 1_000_000;

run();

sub run {
    my @substitutions = (
        sub { s/dolor/new1/ig   },
        sub { s/fuga/new2/ig    },
        sub { s/facilis/new3/ig },
        sub { s/tempo/new4/ig   },
        sub { s/magni/new5/ig   },
    );

    my @times;
    for (1 .. 5) {
        my $data = read_input();
        my $t0 = time;
        find_and_replace($data, \@substitutions);
        push @times, time - $t0;
    }

    printf "%.4f\n", sum(@times)/@times;

    return;
}

sub find_and_replace {
    my $data = shift;
    my $substitutions = shift;

    for ( @$data ) {
        for my $s ( @$substitutions ) {
            $s->();
        }
    }
    return;
}

{
    my @input;
    sub read_input {
        @input
            or @input = map fake_sentences(1)->(), 1 .. INPUT_SIZE;
        return [ @input ];
    }
}

在这种情况下,每次调用find_and_replace我的笔记本电脑大约需要 2.3 秒。五次复制在大约 30 秒内运行。开销是生成 1,000,000 个句子数据集并将其复制四次的综合成本。

于 2017-05-09T13:39:25.897 回答