【问题标题】:Is Perl's unpack() ever faster than substr()?Perl 的 unpack() 是否比 substr() 快?
【发布时间】:2009-07-05 00:55:16
【问题描述】:

我多次读到unpack()substr() 快,尤其是随着子字符串数量的增加。但是,该基准表明并非如此。我的基准测试是否存在缺陷,或者unpack() 的所谓性能优势是旧版本 Perl 的保留?

use strict;
use warnings;
use Benchmark;

my ($data, $format_string, $n_substrings);

my %methods = (
    unpack => sub { return unpack $format_string, $data },
    substr => sub { return map {substr $data, $_, 1} 0 .. $n_substrings - 1 },
);

for my $exp (1 .. 5){
    $n_substrings = 10 ** $exp;
    print $n_substrings, "\n";
    $format_string = 'a1' x $n_substrings;
    $data          =   9  x $n_substrings;
    Benchmark::cmpthese -2, \%methods;
}

输出(在 Windows 上):

10
           Rate unpack substr
unpack 131588/s     --   -52%
substr 276802/s   110%     --
100
          Rate unpack substr
unpack 13660/s     --   -57%
substr 31636/s   132%     --
1000
         Rate unpack substr
unpack 1027/s     --   -68%
substr 3166/s   208%     --
10000
         Rate unpack substr
unpack 84.4/s     --   -74%
substr  322/s   281%     --
100000
         Rate unpack substr
unpack 5.46/s     --   -82%
substr 30.1/s   452%     --

正如一些答案所指出的,unpack() 在 Windows 上表现不佳。这是 solaris 机器上的输出 - 几乎没有那么决定性,但 substr() 仍然赢得了竞速:

10
           Rate unpack substr
unpack 202274/s     --    -4%
substr 210818/s     4%     --
100
          Rate unpack substr
unpack 22015/s     --    -9%
substr 24322/s    10%     --
1000
         Rate unpack substr
unpack 2259/s     --    -9%
substr 2481/s    10%     --
10000
        Rate unpack substr
unpack 225/s     --    -9%
substr 247/s     9%     --
100000
         Rate unpack substr
unpack 22.0/s     --   -10%
substr 24.4/s    11%     --

【问题讨论】:

  • 没有什么比什么都快。您必须将其应用于特定问题。猎豹比鹅快吗?也许超过 100 米,但不是跨越大洋。 :)
  • @brian 我同意你的观点,但评论似乎偏离了目标。这是一个更接近的类比:“猎豹曾经比鹅快吗?这里有一个特定的例子,说明它们互相比赛。我的测试有偏差吗?”?答案:“是的,有偏见。鹅是在类固醇上。”

标签: perl


【解决方案1】:

事实上,您的基准测试有缺陷的,以一种非常非常有趣的方式,但归根结底是您真正比较的是 unpack vs .map 可以扔掉一个列表,因为 Benchmark::cmpthese() 在 void 上下文中执行函数。

你的 substr 排名靠前的原因是 pp_ctl.c pp_mapwhile() 中的这行代码:

if (items && gimme != G_VOID) {

即如果 perl 的 map 知道它是在 void 上下文中被调用,它会神奇地跳过一堆工作(即为 map 的结果分配存储空间)!

(我对 windows 的预感与上面看到的其他情况相比,基于 windows 的 perl 内存分配很糟糕,因此跳过分配可以节省更多 - 只是预感,我没有 windows 框玩玩。但实际的解包实现是直接的 C 代码,与 Windows 和其他的应该没有太大区别。)

我有三种不同的解决方案来解决这个问题并进行更公平的比较:

  1. 将列表分配给数组
  2. 循环遍历函数内的列表,不返回任何内容
  3. 返回对列表的引用(隐藏 void 上下文)

这是我的 %methods 版本,包含所有三个版本:

my %methods = (
    unpack_assign => sub { my @foo = unpack $format_string, $data; return },
    unpack_loop => sub { for my $foo (unpack $format_string, $data) { } },
    unpack_return_ref => sub { return [ unpack $format_string, $data ] },
    unpack_return_array => sub { return unpack $format_string, $data },

    substr_assign => sub { my @foo = map {substr $data, $_, 1} 0 .. ($n_substrings - 1) },
    substr_loop => sub { for my $foo ( map {substr $data, $_, 1} 0 .. ($n_substrings - 1)) { } },
    substr_return_ref => sub { return [ map {substr $data, $_, 1} 0 .. ($n_substrings - 1) ] },
    substr_return_array => sub { return map { substr $data, $_, 1} 0 .. ($n_substrings - 1) },
);

我的结果:

$ perl -v

This is perl, v5.10.0 built for x86_64-linux-gnu-thread-multi

$ perl foo.pl
10
                        Rate substr_assign substr_return_ref substr_loop unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       101915/s            --              -20%        -21%          -28%              -51%        -51%                -65%                -69%
substr_return_ref   127224/s           25%                --         -1%          -10%              -39%        -39%                -57%                -62%
substr_loop         128484/s           26%                1%          --           -9%              -38%        -39%                -56%                -61%
unpack_assign       141499/s           39%               11%         10%            --              -32%        -32%                -52%                -57%
unpack_return_ref   207144/s          103%               63%         61%           46%                --         -1%                -29%                -37%
unpack_loop         209520/s          106%               65%         63%           48%                1%          --                -28%                -37%
unpack_return_array 292713/s          187%              130%        128%          107%               41%         40%                  --                -12%
substr_return_array 330827/s          225%              160%        157%          134%               60%         58%                 13%                  --
100
                       Rate substr_assign substr_loop substr_return_ref unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       11818/s            --        -25%              -25%          -26%              -53%        -55%                -63%                -70%
substr_loop         15677/s           33%          --               -0%           -2%              -38%        -40%                -51%                -60%
substr_return_ref   15752/s           33%          0%                --           -2%              -37%        -40%                -51%                -60%
unpack_assign       16061/s           36%          2%                2%            --              -36%        -39%                -50%                -59%
unpack_return_ref   25121/s          113%         60%               59%           56%                --         -4%                -22%                -35%
unpack_loop         26188/s          122%         67%               66%           63%                4%          --                -19%                -33%
unpack_return_array 32310/s          173%        106%              105%          101%               29%         23%                  --                -17%
substr_return_array 38910/s          229%        148%              147%          142%               55%         49%                 20%                  --
1000
                      Rate substr_assign substr_return_ref substr_loop unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       1309/s            --              -23%        -25%          -28%              -52%        -54%                -62%                -67%
substr_return_ref   1709/s           31%                --         -3%           -6%              -38%        -41%                -51%                -57%
substr_loop         1756/s           34%                3%          --           -3%              -36%        -39%                -49%                -56%
unpack_assign       1815/s           39%                6%          3%            --              -34%        -37%                -48%                -55%
unpack_return_ref   2738/s          109%               60%         56%           51%                --         -5%                -21%                -32%
unpack_loop         2873/s          120%               68%         64%           58%                5%          --                -17%                -28%
unpack_return_array 3470/s          165%              103%         98%           91%               27%         21%                  --                -14%
substr_return_array 4015/s          207%              135%        129%          121%               47%         40%                 16%                  --
10000
                     Rate substr_assign substr_return_ref substr_loop unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       131/s            --              -23%        -27%          -28%              -52%        -55%                -63%                -67%
substr_return_ref   171/s           30%                --         -5%           -6%              -38%        -42%                -52%                -57%
substr_loop         179/s           37%                5%          --           -1%              -35%        -39%                -50%                -55%
unpack_assign       181/s           38%                6%          1%            --              -34%        -38%                -49%                -55%
unpack_return_ref   274/s          109%               60%         53%           51%                --         -6%                -23%                -32%
unpack_loop         293/s          123%               71%         63%           62%                7%          --                -18%                -27%
unpack_return_array 356/s          171%              108%         98%           96%               30%         21%                  --                -11%
substr_return_array 400/s          205%              134%        123%          121%               46%         37%                 13%                  --
100000
                      Rate substr_assign substr_return_ref substr_loop unpack_assign unpack_return_ref unpack_loop unpack_return_array substr_return_array
substr_assign       13.0/s            --              -22%        -26%          -29%              -51%        -55%                -63%                -67%
substr_return_ref   16.7/s           29%                --         -5%           -8%              -37%        -43%                -52%                -58%
substr_loop         17.6/s           36%                5%          --           -3%              -33%        -40%                -50%                -56%
unpack_assign       18.2/s           40%                9%          3%            --              -31%        -37%                -48%                -54%
unpack_return_ref   26.4/s          103%               58%         50%           45%                --         -9%                -25%                -34%
unpack_loop         29.1/s          124%               74%         65%           60%               10%          --                -17%                -27%
unpack_return_array 35.1/s          170%              110%         99%           93%               33%         20%                  --                -12%
substr_return_array 39.7/s          206%              137%        125%          118%               50%         36%                 13%                  --

回到最初的问题:“unpack() 比 substr() 快吗?”回答:总是,对于这种类型的应用程序——除非你不关心返回值;)

【讨论】:

  • 这与我在精通 Perl 的基准测试章节中讨论的情况几乎相同。在 void 上下文中,grep 真的很快!
【解决方案2】:

测试没有缺陷,但有偏差。如果您需要做的只是从字符串中提取一个相当简单的子字符串,那么 substr 会更好,但仅此而已。例如,即使是这个简单的任务,使用 substr 也不容易完成:

$foo = '123foo456789bar89012';
my ($t1,$t2,$t3,$t4,$t5) = unpack("A3A3A6A3A5",$foo);

这就是你应该看到 substr 和 unpack 之间的巨大差异的地方。

【讨论】:

    【解决方案3】:

    我在 Ubuntu 9 下得到了与提问者相似的结果:

    This is perl, v5.10.0 built for i486-linux-gnu-thread-multi
    10
           Rate unpack substr
    unpack 535925/s     --    -3%
    substr 552749/s     3%     --
    100
          Rate unpack substr
    unpack 57957/s     --    -5%
    substr 61264/s     6%     --
    1000
         Rate unpack substr
    unpack 4716/s     --   -22%
    substr 6075/s    29%     --
    10000
        Rate unpack substr
    unpack 466/s     --   -24%
    substr 609/s    31%     --
    100000
         Rate unpack substr
    unpack 46.3/s     --   -23%
    substr 60.5/s    31%     --
    

    但我不确定这是否相关。我不倾向于使用 unpack 进行简单的字符串提取,因为它的格式字符串不洁:-)

    我认为它会发光的地方是提取编码整数和各种其他二进制信息,这是我将使用它的地方。

    您应该从 Matthew 和我(以及您)的基准测试中了解到的一点是,它在很大程度上取决于环境因素。请记住,速度虽然不错,但并不是最重要的——我认为我编写的代码不会因为每秒只能执行 460 万次提取而受到严重影响,而不是600 万 :-) 你可能需要额外的性能,但我怀疑大多数用 Perl 编写的应用程序。

    【讨论】:

    • +1 通常,但特别是关于解包格式字符串...我特别同意你的观点,即打包/解包是为二进制数据设计的,而不是简单地获取子字符串。
    【解决方案4】:

    自从提出这个问题以来,我在各种条件下又多次对 substrunpack 进行了基准测试。以下是我学到的一些东西:

    • 不要以某种方式设置基准 在 void 中调用 Perl 函数 上下文(正如我在最初的问题中所做的那样;参见 dlowe 的有益回应)。 一些 Perl 函数有 调用时的优化 无效上下文(以及这些优化 似乎因操作系统而异),可能 扭曲您的基准测试结果。

    • 如果您对substr 的使用涉及 循环(例如,迭代 列位置列表),unpack 总是更快。然而 substr 在此明显缓慢 情况是由于开销 循环,而不是 substr 本身。

    • 如果只需要几个字段, substr 通常更快或 快到unpack

    • 如果有多个字段 必需的,面对面的比较 在unpack 和等价物之间 substr 呼叫的数量不变 与字段数一样多 增加:两种方法都变成 以同样的速度变慢。

    • 结果可能因操作系统而异。 在我的 Windows XP 机器上,unpack 每当超过 需要几个字段。在我们的 我工作场所的 Solaris 机器, substr 总是更快,甚至进入 数百个字段。

    底线:无论字段数量如何,unpacksubstr 的性能都不是什么大问题。使用产生最清晰代码的任何方法。但是,如果您发现自己在循环结构中使用 substr,则切换到 unpack 会显着提高速度。

    【讨论】:

      【解决方案5】:

      并不是说我不信任您的结果,而是您在什么样的系统上运行它?我在 Ubuntu 8.10 (perl 5.10) 上运行了您的脚本,结果如​​下:

       mscharley@S04:~$ perl -v
      
      This is perl, v5.10.0 built for x86_64-linux-gnu-thread-multi
       mscharley@S04:~$ ./test.pl
      10
                 Rate substr unpack
      substr 587390/s     --   -10%
      unpack 650343/s    11%     --
      100
                Rate substr unpack
      substr 66060/s     --    -5%
      unpack 69433/s     5%     --
      1000
               Rate substr unpack
      substr 6847/s     --    -2%
      unpack 6977/s     2%     --
      10000
              Rate substr unpack
      substr 683/s     --    -1%
      unpack 693/s     1%     --
      100000
               Rate substr unpack
      substr 68.3/s     --    -0%
      unpack 68.4/s     0%     --
      

      我的本​​地 Windows 机器的结果(从我的结果来看,我猜你正在使用):

      >perl -v
      
      This is perl, v5.10.0 built for MSWin32-x86-multi-thread
      
      >perl test.pl
      10
                 Rate unpack substr
      unpack 125210/s     --   -50%
      substr 252878/s   102%     --
      100
                Rate unpack substr
      unpack 12677/s     --   -56%
      substr 28854/s   128%     --
      1000
               Rate unpack substr
      unpack  963/s     --   -66%
      substr 2846/s   196%     --
      10000
               Rate unpack substr
      unpack 78.8/s     --   -73%
      substr  291/s   269%     --
      100000
               Rate unpack substr
      unpack 4.88/s     --   -82%
      substr 27.2/s   457%     --
      

      如果我必须很好地猜测差异,我会大胆猜测并说 Windows 没有本机打包/解包功能,因此 Perl 必须以某种方式模拟它。反正我的 2c。

      【讨论】:

      • 我在 Ubuntu 8.10 上得到了类似的结果。
      • 是的...我敢打赌 Windows 上的性能不佳是由于打包/解包的本地实现不佳(或缺乏)...
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-01
      • 1970-01-01
      • 2016-07-14
      • 2012-11-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多