【发布时间】:2016-06-08 19:16:37
【问题描述】:
简介:
我通常不使用输出参数(作为输入和输出值或仅作为输出的参数)。这是为了提高我的代码的可读性和可维护性。但是话又说回来,前几天我突然有一个嵌套很深的子程序调用。我发现自己正在尝试优化它以提高速度。
这是一个示例:我有一个字符串,表示从文件中读取的空白修剪行。如果该行是空的,我想用代表返回键的 unicode 符号替换它。假设我在 google 上搜索并发现符号 ↵(unicode U+21B5)看起来不错[1]。于是我写了一个简短的子程序:
sub handle_empty_lines {
my ( $str ) = @_;
if ( (!defined $str) || $str eq '' ) {
return "\x{21B5}";
}
return $str;
}
我是这样使用它的:
$line = handle_empty_lines( $line );
现在,我想优化这个调用,但代码仍然可读和可维护。
第一个选项是内联:
$line = "\x{21B5}" if (!defined $str) || $str eq '';
当然是自然而快速的,但让我们假设我不想用 if 语句2 弄乱代码并拒绝这个选项。
这里有另外两个选项,
传递对
$str的引用以避免在子例程中复制输入参数(即:将按值调用转换为按引用调用),或利用 Perl 的内置引用机制调用。
这两个选项都引入了"input/output argument"(即:作为输入和输出的参数),从而降低了代码的可读性并使维护更加困难(在我看来)。
让第三个选项是保留原始版本(按值调用)。以下是三个选项的快速比较,仅用于速度(不是可读性)。
use strict;
use warnings;
use Benchmark qw(timethese);
my $str1 = '';
timethese(
2_000_000,
{
case1 => sub { my $test = $str1; case1( \$test ) },
case2 => sub { my $test = $str1; case2( $test ) },
case3 => sub { my $test = $str1; $test = case3( $test ) },
}
);
sub case1 {
if ( (!defined $$_[0]) || $$_[0] eq '' ) {
$$_[0] = "\x{21B5}";
}
}
sub case2 {
if ( (!defined $_[0]) || $_[0] eq '' ) {
$_[0] = "\x{21B5}";
}
}
sub case3 {
my ( $str ) = @_;
if ( (!defined $str) || $str eq '' ) {
return "\x{21B5}";
}
return $str;
}
输出是(Ubuntu 笔记本电脑,Intel(R) Core(TM) i7-4702MQ CPU @ 2.20GHz):
Benchmark: timing 2000000 iterations of case1, case2, case3...
case1: 1 wallclock secs ( 0.84 usr + 0.00 sys = 0.84 CPU) @ 2380952.38/s (n=2000000)
case2: 1 wallclock secs ( 0.45 usr + 0.00 sys = 0.45 CPU) @ 4444444.44/s (n=2000000)
case3: 1 wallclock secs ( 0.70 usr + 0.00 sys = 0.70 CPU) @ 2857142.86/s (n=2000000)
请注意,案例 2 比案例 1 快 87%3,比案例 3 快 56%。
有趣的是,引用调用(案例 1)比值调用(案例 3)慢。
问题:
假设我现在想保留案例 2:
sub handle_empty_lines {
if ( (!defined $_[0]) || $_[0] eq '' ) {
$_[0] = "\x{21B5}";
}
}
然后,如果我使用:
handle_empty_lines( $line );
它没有告诉读者它修改了$line。
我应该如何处理这个问题?我可以想到两个选择:
-
在通话后发表评论:
handle_empty_lines( $line ); # Note: modifies $line -
更改子程序的名称。取一个能表明
$line被修改的读者,例如:handle_empty_lines__modifies_arg( $line );
脚注:
1. 后来我发现我可以使用N{} 转义来使代码更具可读性,使用“\N{DOWNWARDS ARROW WITH CORNER LEFTWARDS}”而不是“\x{21B5}”
2.对于这个简单的案例,我同意这是否可以称为任何形式的混乱都是值得怀疑的。
3. 4444444.44 / 2380952.38 = 1.87
【问题讨论】:
-
Re "注意案例 2 比案例 1 快 87%3,比案例 3 快 56%。"曾使用
cmpthese而不是timethese -
@ikegami 是的,我考虑过使用
cmpthese,但我发现这些数字让我感到困惑 -
您的每个功能都以两种方式与其他功能不同。明显缺席:
sub case4 { if ( (!defined $_[0]) || $_[0] eq '' ) { return "\x{21B5}"; } return $_[0]; } -
最好的建议是根本不要使用输出参数。副作用是一种必要的邪恶,它们使你的代码更难组合和推理,所以尽可能避免它们。修复对函数的调用,而不是函数本身。
标签: perl maintenance