为什么使用Inline :: C用虚函数替换Perl的///导致显着减速?

Zip*_*970 2 perl performance inline-c

我有一个大约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;
}
Run Code Online (Sandbox Code Playgroud)

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

我对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;

}
Run Code Online (Sandbox Code Playgroud)

在我的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)
Run Code Online (Sandbox Code Playgroud)

Pure Perl的速度是调用空C函数的两倍.完全不是我所期待的!再说一遍,我以前从未使用过Inline :: C,所以也许我在这里错过了一些东西?

Sin*_*nür 6

在使用的版本中Inline::C,您保留了原始纯Perl脚本中的所有内容,并且只更改了一件事:此外,您已经用s///更差的实现替换了Perl的高度优化.调用你的虚函数实际上涉及工作,而s///在这种情况下没有一个调用做很多.这是一个先验不可能的Inline::C版本,运行速度更快.

在C方面,功能

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

  return text;

}
Run Code Online (Sandbox Code Playgroud)

不是"无所事事"的功能.调用它涉及解压缩参数.指向的字符串text必须复制到返回值.每次调用都需要支付一些开销.

鉴于s///没有替代品,没有涉及复制.此外,Perl s///还经过高度优化.您确定可以编写更好的查找和替换,以便更快地弥补调用外部函数的开销吗?

如果使用以下实现,则应获得可比较的速度:

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;

}
Run Code Online (Sandbox Code Playgroud)
Benchmark: timing 5 iterations of Inline C, Pure Perl...
  Inline C:  6 wallclock secs ( 5.69 usr +  0.00 sys =  5.69 CPU) @  0.88/s (n=5)
 Pure Perl:  6 wallclock secs ( 5.70 usr +  0.00 sys =  5.70 CPU) @  0.88/s (n=5)

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

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

此外,由于你的问题是令人尴尬的并行,你最好将工作分成尽可能多的块来处理.

例如,看看基准游戏中regex-redux任务中的Perl条目:

Perl#4(仅限前叉):14.13秒

和

Perl#3(fork&threads):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 ];
    }
}
Run Code Online (Sandbox Code Playgroud)

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