在阅读“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运行慢很多。输出虽然偏离了这一观察结果。我在这里缺少什么?
答案 0 :(得分:15)
对我来说,输出看起来有点不同:
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)
看一下代码告诉我那两个潜艇可能花了大部分时间来整理文件,所以我这样做了:
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
在标量上下文中,行为 “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变换将在您以有意义的方式使用它时为您提供帮助。当您以有意义的方式进行基准测试时,基准测试将显示此信息。
答案 1 :(得分:5)
除了Manni的优秀答案之外,另一个要考虑的因素是您的示例中可能会进行一些缓存,例如:在文件系统级别。
如果多次访问同一个文件,FS可能会进行一些缓存,导致Schwartzian变换节省的时间比预期的少。
答案 2 :(得分:4)
要彻底检查此案例,请参阅我的Perlmonks帖子"Wasting Time Thinking about Wasted Time"。我还在“基准测试”中对此进行了扩展 Mastering Perl中的一章。正如其他人已经注意到的那样,基准代码就是问题,而不是变换。这是Intermediate Perl中的错误。
但是,要回答您的问题,当排序键计算很昂贵并且您必须多次计算时,缓存键变换很有用。在缓存排序键的额外工作和保存它的周期之间需要权衡。通常,您需要排序的元素越多,缓存密钥转换将执行的效果越好。
答案 3 :(得分:3)
我知道这在技术上已经完全得到了回答,但我有一些相关的附注。
首先,我通常更喜欢cmpthese()来计算时间(),因为它会以人类可读和信息丰富的方式告诉你哪个更好,而不仅仅是呈现时间。
其次,对于像这样的理论问题,我通常会尽可能地避免系统调用,因为内核可以让你永远等待,如果它有心情这样做 - 不是真正公平的测试。
Thrid,有趣的是,如果列表已经排序,变换总是更昂贵:如果设置$ 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;
}