【问题标题】:When are Schwartzian Transforms useful?施瓦茨变换什么时候有用?
【发布时间】:2010-10-10 07:18:39
【问题描述】:

在阅读“Intermediate Perl”一书时,我注意到有关 Schwartzian 变换的部分并尝试了练习 (9.9.2) 中的示例,但注意到多次运行导致变换比正常排序花费更多时间。此处的代码根据文件大小对 windows\system32 目录中的文件进行简单排序 -

#!/usr/bin/perl
use strict;
use warnings;

use Benchmark;

my $time = timethese( 10, {
            testA => sub { map $_->[0],     
                        sort {$a->[1] <=> $b->[1]}
                        map [$_, -s $_],
                        glob "C:\\Windows\\System32\\*";
                    },
            testB => sub { sort { -s $a <=> -s $b } glob "C:\\Windows\\System32\\*";
                    },
            }
        );

输出是 -

Benchmark: timing 10 iterations of testA, testB...
     testA: 11 wallclock secs ( 1.89 usr +  8.38 sys = 10.27 CPU) @  0.97/s (n=10)
     testB:  5 wallclock secs ( 0.89 usr +  4.11 sys =  5.00 CPU) @  2.00/s (n=10)

我的理解是,由于文件操作 (-s) 需要在 testB 案例中一遍又一遍地重复,它的运行速度应该比 testA 慢很多。输出虽然偏离了该观察。我在这里错过了什么?

【问题讨论】:

    标签: perl transform benchmarking


    【解决方案1】:

    有关此案例的全面检查,请参阅我的Perlmonks 帖子"Wasting Time Thinking about Wasted Time"。我还在“基准测试”中对此进行了扩展 章节在Mastering Perl。正如其他人已经指出的那样,问题在于基准测试代码,而不是转换。这是Intermediate Perl的错误。

    但是,为了回答您的问题,当排序键计算成本很高并且您必须多次计算时,缓存键转换很有用。在缓存排序键的额外工作和节省的周期之间需要权衡。通常,您需要排序的元素越多,缓存键转换的性能就越好。

    【讨论】:

    • 哇。这么多年我怎么能错过呢?
    【解决方案2】:

    对我来说,输出看起来有点不同:

     testA:  1 wallclock secs ( 0.16 usr +  0.11 sys =  0.27 CPU) @ 37.04/s (n=10)
            (warning: too few iterations for a reliable count)
     testB:  0 wallclock secs ( 0.09 usr +  0.02 sys =  0.11 CPU) @ 90.91/s (n=10)
            (warning: too few iterations for a reliable count)
    

    以相当大的迭代值(我选择 100,000)作为基准,我得到了:

     testA: 23 wallclock secs (12.15 usr + 10.05 sys = 22.20 CPU) @ 4504.50/s (n=100000)
     testB: 11 wallclock secs ( 6.02 usr +  5.57 sys = 11.59 CPU) @ 8628.13/s (n=100000)
    

    查看代码告诉我,这两个 sub 可能大部分时间都在查找文件,所以我这样做了:

    my @files = glob "C:\\Windows\\System32\\*";
    my $time = timethese( 1_000_000, {
                    testA => sub {
                                    map $_->[0],
                                        sort {$a->[1] <=> $b->[1]}
                                            map [$_, -s $_],
                                                 @files;
                             },
                    testB => sub {
                                sort { -s $a <=> -s $b } @files;
                             },
                    }
            );
    

    得到:

     testA: 103 wallclock secs (56.93 usr + 45.61 sys = 102.54 CPU) @ 9752.29/s (n=1000000)
     testB: -1 wallclock secs ( 0.12 usr +  0.00 sys =  0.12 CPU) @ 8333333.33/s (n=1000000)
            (warning: too few iterations for a reliable count)
    

    这里有点腥味,不是吗?

    那么,让我们看一下文档:

    perldoc -f 排序

    在标量上下文中, "sort()" 未定义。

    啊哈!所以让我们再试一次:

    my @files = glob "C:\\Windows\\System32\\*";
    my $time = timethese( 100_000, {
                    testA => sub {
                                  my @arr=  map $_->[0],
                                        sort {$a->[1] <=> $b->[1]}
                                            map [$_, -s $_],
                                                 @files;
                             },
                    testB => sub {
                                my @arr = sort { -s $a <=> -s $b } @files;
                             },
                    }
            );
    

    这给了我:

     testA: 12 wallclock secs ( 7.44 usr +  4.55 sys = 11.99 CPU) @ 8340.28/s (n=100000)
     testB: 34 wallclock secs ( 6.44 usr + 28.30 sys = 34.74 CPU) @ 2878.53/s (n=100000)
    

    所以。回答您的问题:只要您以有意义的方式使用 Schwartzian 变换,它就会对您有所帮助。当您以有意义的方式进行基准测试时,基准测试将显示这一点。

    【讨论】:

    • 问题在于您提到的 sort 返回标量值。改变它解决了这个问题。您能否检查一下地图表达式是否“错误”?该手册确实声明地图作为地图块列表//地图EXPR,LIST。就我而言,它们都工作得很好。
    • 我认为我收到的警告是由使用地图触发的。我会检查的。
    • 一种可能的解释是警告是因为目录条目返回的大小为 0。这是在 $a->[1] 或 $b->[1] 中存储为 undef ?
    • 没有。但你让我走上了正轨。我最初用于测试的目录包含一个非常非常奇怪的文件。我会删除那个臭虫,然后稍微修改一下我的答案。
    • 历史记录:已经为标量上下文排序建议了足够多的不同潜在行为,因此决定最好将其正式保留为未定义。这是一个:nntp.perl.org/group/perl.perl5.porters/2004/06/msg92477.html
    【解决方案3】:

    我知道这在技术上已经得到了相当完整的回答,但我有一些相关的附注。

    首先,我通常更喜欢 cmpthese() 而不是 timethese(),因为它以人类可读和信息丰富的方式告诉您哪个更好,而不仅仅是显示时间。

    其次,对于这样的理论问题,我通常会尽可能完全避免系统调用,因为如果内核愿意这样做,它会让你永远等待——这不是一个真正公平的测试。

    第三,有趣的是,如果列表已经排序,则转换总是更昂贵:如果您设置 $point_of_interest=2,则转换获胜;但如果您设置 $point_of_interest=1,则常规排序将获胜。我觉得这个结果很有趣,值得一提。

    use strict;
    use Benchmark qw(cmpthese);
    
    my $point_of_interest = 2;
    my @f = (1 .. 10_000) x $point_of_interest;
    my @b;
    
    cmpthese( 500, {
        transform => sub {
            @b = 
            map  {$_->[0]}
            sort {$a->[1] <=> $b->[1]}
            map  {[$_, expensive($_)]}
            @f
        },  
        vanilla => sub { @b = sort { expensive($a) <=> expensive($b) } @f },
    }); 
    
    sub expensive {
        my $arg = shift;
    
        $arg .= "_3272";
        $arg += length "$arg";
        $arg = $arg ** 17;
        $arg = sqrt($arg);
    
        $arg;
    }   
    

    【讨论】:

    • 奇怪的是,在这篇文章之前我不是骗子,但之后我是。无论如何,我的 cmpthese() 似乎总是更快地找到香草,尽管我的 timethese() 似乎显示了我上面声称的内容。
    • 我得到了 cmpthese 和 timethese 的预期结果。我不同意您关于 cmpthese 输出的可读性的观点。我觉得很难读。也许,也许这就是你现在认为自己是骗子的原因?
    • 不,显然这是主观的。我想我实际上更喜欢 timethese(),因为我注意到 cmpthese() 下的结果有多么不同。我想知道为什么会有差异。似乎也发生在我的 perl5.6.1 下。
    • 你真的认为 cmpthese 给了你错误的结果吗?冒着重复自己的风险,我个人发现很难阅读 cmpthese 的输出,并且经常混淆最快和最慢的结果。
    • 我可能只是误读了它们,因为我现在得到了我期望的结果。我更有可能只是读错了单词而不是输出 - 我有阅读障碍。
    【解决方案4】:

    除了 Manni 的出色回答之外,另一个需要考虑的因素是您的示例中可能正在进行一些缓存,例如在文件系统级别。

    如果多次访问同一个文件,FS 可能会进行一些缓存,从而导致 Schwartzian 变换节省的时间比预期的要少。

    【讨论】:

    • 好点。获取排序标准的成本越高,转换的效果就越显着。
    猜你喜欢
    • 1970-01-01
    • 2011-06-08
    • 2015-06-15
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    • 2021-10-08
    • 2021-10-04
    • 2011-09-28
    相关资源
    最近更新 更多