如何使用二进制、变量查找和 foreach 优化代码?

How to optimize code using binary, variable lookup and foreach?

如何优化此 Perl 脚本?

它需要“星期几”,例如“星期一和星期二”或“星期三、星期四和星期日”,并且只是 returns 一个整数来表示它们。

use strict;
use feature qw(signatures);
use warnings;
no warnings qw(experimental::signatures);
my $DAYS_OF_WEEK_LOOKUP = ( { mon  =>  1, tue  =>  2, wed  =>  4, thu  =>  8, fri  => 16, sat  => 32, sun  => 64 });

my $days_of_week_integer= days_of_week_to_int (['zzz', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'fri', 'SAT']);
print "$days_of_week_integer\n";


sub days_of_week_to_int ($days) {
  my $day_of_week = 0;
  foreach my $key (@$days)  {
    $day_of_week += $DAYS_OF_WEEK_LOOKUP->{lc($key)} if $DAYS_OF_WEEK_LOOKUP->{lc($key)};
  }
  return $day_of_week if $day_of_week < 128;
  return;
}

我一直在寻求了解如何更好地优化我的代码并从经验丰富的开发人员那里获得经验。该脚本运行得非常好,而且它是在 Perl 中运行的,所以它已经运行得非常快。

但是,我不禁觉得有一种更优化的“Perl 方式”可以达到相同的结果,我想知道是否有人有同样的感觉并能告诉我方法。小技巧就是重要技巧!

谢谢大家,我在此处添加带有基准测试的最终代码。这是我的原始代码,去除了额外的开销,但忠实于北极熊代码的原始代码。

use strict;
use Benchmark ':all';

my $polar_bitmap = 0x01;
my @polar_days_of_week = qw/Mon Tue Wed Thu Fri Sat Sun/;         # USA/Canada week days order
my $orig_DAYS_OF_WEEK_LOOKUP = ( { Mon  =>  1, Tue  =>  2, Wed  =>  4, Thu  =>  8, Fri  => 16, Sat  => 32, Sun  => 64 });
my %polar_DAYS_OF_WEEK_LOOKUP = map { $_ => ( $polar_bitmap <<= 1 ) / 2 } @polar_days_of_week;

my @days;
@days  = qw/Mon Wed Tue Sat Sun Thu/;

print "orig  : " . (orig_days_of_week_to_int(@days)) . "\n";
print "polar : " . (polar_days_to_mask    ( \@days)) . "\n";

@days = qw/Mon Sat Sun Thu/;

print "polar : " . (polar_days_to_mask(\@days)) . "\n";
print "orig  : " . (orig_days_of_week_to_int(@days)) . "\n";

@days = qw/Mon Wed Tue Sat Sun Thu/;

cmpthese(-3, {
    orginal  => sub { orig_days_of_week_to_int(@days) },
    polar_b  => sub { polar_days_to_mask(\@days)      },
});

exit 0;

sub polar_days_to_mask {
    my $days = shift;
    my $mask;

    $mask ^= $polar_DAYS_OF_WEEK_LOOKUP{$_} for @$days;

    return $mask;
}

sub orig_days_of_week_to_int  {
  my @days = @_;
  my $day_of_week = 0;
  foreach my $key (@days)  {
    $day_of_week += $orig_DAYS_OF_WEEK_LOOKUP->{$key};
  }
  return $day_of_week;
}

并且输出:

orig  : 111
polar : 111
polar : 105
orig  : 105
             Rate orginal polar_b
orginal 1411082/s      --    -41%
polar_b 2398719/s     70%      --

我已经添加了上面的代码,以防万一我搞砸了任何事情,因为您始终应该小心使用 BenchMarks! :-)

为了证明以上观点...我想我会用我拥有的 LINODE 节点对其进行测试...

            Rate orginal polar_b
orginal 741061/s      --    -25%
polar_b 992490/s     34%      --

实际上,与我在家使用的 HP 裸机相比...LINODE 始终如一地为我提供 34% 的性能,而我的家用机器为 70%。 (相同o/s,相同版本)

所以你是...警惕基准!

尽管如此,一半是人,一半是熊,一半是 AI,Polar 解决方案很酷。

我没有做过任何基准测试,但我认为这更干净(评论中的注释):

#!/usr/bin/env perl
use strict;
use warnings;
use experimental qw/signatures/; # Instead of use feature and no warnings
use feature qw/say/;
use List::Util qw/sum0/;

# Plain hash instead of hashref
my %DAYS_OF_WEEK_LOOKUP = ( mon => 1, tue => 2, wed => 4, thu => 8,
                            fri => 16, sat => 32, sun => 64 );

# Function takes a list of values instead of a single arrayref
my $days_of_week_integer = days_of_week_to_int('zzz', 'Sun', 'Mon',
                                               'Tue', 'Wed', 'Thu', 'fri', 'SAT');

# say instead of "print "$foo\n"
say $days_of_week_integer;

sub days_of_week_to_int (@days) {
    no warnings qw/uninitialized/; # Turn off warning about undef values
    # Sum up the results of a hash slice instead of using an explicit loop
    my $day_of_week = sum0 @DAYS_OF_WEEK_LOOKUP{map { lc } @days};
    return $day_of_week < 128 ? $day_of_week : undef;
}

可以通过一些小的调整来加快速度:使用 map 而不是手动迭代并使用库添加数字,使用短路测试,因此在这种情况下很少这样做每次 if 测试。更重要的是,这可能更具可读性。

use warnings;
use strict;
use feature 'say';

use List::Util qw(sum0);
use Benchmark qw(cmpthese);

my $runfor = shift // 3;

my $DAYS_OF_WEEK = { mon => 1, tue => 2, wed => 4, thu => 8, fri => 16, 
    sat => 32, sun => 64 };    

sub add_days_hr {
    my $days = shift;
    my $ndow = sum0 map { $DAYS_OF_WEEK->{lc($_)} // () } @$days;
    return $ndow < 128 ? $ndow : undef;
}

sub dow_to_int {  # original code (from question)
    my $days = shift;
    my $day_of_week = 0;
    foreach my $key (@$days)  {
        $day_of_week += $DAYS_OF_WEEK->{lc($key)} if $DAYS_OF_WEEK->{lc($key)};
    }   
    return $day_of_week if $day_of_week < 128;
    return;
}
    
my @test_days = qw(zzz Sun Mon Tue Wed Thu fri SAT);

cmpthese( -$runfor, { 
    add_days_hr => sub { add_days_hr( \@test_days ) },
    dow_to_int  => sub { dow_to_int ( \@test_days ) },
});

打印,在装有 5.16

的旧笔记本电脑上
                Rate  dow_to_int add_days_hr
dow_to_int  526696/s          --        -12%
add_days_hr 599792/s         14%          --

通过使用散列而不是 hashref 来避免取消引用以及使用无需取消引用的平面输入列表(数组而不是 arrayref),这可以更快一些,但这已经占用了预期的界面为了小利。

如果重复执行此操作,则可以通过内联它来加快速度,因为子调用在 Perl 中非常耗时。


在服务器上,还有两个案例

                 Rate   add_other  dow_to_int add_days_hr    add_days
add_slice    900635/s          --         -5%        -17%        -21%
dow_to_int   952705/s          6%          --        -12%        -16%
add_days_hr 1079487/s         20%         13%          --         -5%
add_days    1133645/s         26%         19%          5%          --

图例:add_slice 是使用 slice 的解决方案(来自 Shawn 的),dow_to_int 原始代码(如上),add_days_hr 上面的解决方案,以及 add_days同一个但使用哈希进行查找(不是 hashref)

# These two need %DAYS_OF_WEEK hash
my %DAYS_OF_WEEK = ( mon => 1, tue => 2, wed => 4, thu => 8, fri => 16,
    sat => 32, sun => 64 );

sub add_slice {  # from Shawn's answer
    my $days = shift;
    no warnings qw/uninitialized/;
    my $day_of_week = sum0 @DAYS_OF_WEEK{map { lc } @$days};
    return $day_of_week < 128 ? $day_of_week : undef;
}

sub add_days {   # With hash lookup instead of hashref, otherwise same
    my $days = shift;
    my $ndow = sum map { $DAYS_OF_WEEK{lc($_)} // () } @$days;
    return $ndow < 128 ? $ndow : undef;
}

看起来 OP 试图根据提供的示例代码生成工作日的位掩码。

请调查以下仅供演示使用的代码片段。使用演示输出随意删除不需要的部分。

注意:代码使用 USA/Canada 日顺序,欧洲移动 Sun 到列表末尾

use strict;
use warnings;
use feature qw(say);

my $bitmap = 0x01;
my @days_of_week = qw/Sun Mon Tue Wed Thu Fri Sat/;         # USA/Canada week days order
my %DAYS_OF_WEEK_LOOKUP = map { $_ => ( $bitmap <<= 1 ) / 2 } @days_of_week;

my @days = qw/Mon Wed Thu Sat/;

say '
 Result
---------------';
printf "Days: %s\n", join(',',@days);
printf "Mask: 0b%08b\n", days_to_mask(\@days);

exit 0;

sub days_to_mask {
    my $days = shift;
    my $mask;
    
    $mask ^= $DAYS_OF_WEEK_LOOKUP{$_} for @$days;
    
    return $mask;
}

输出

 Result
---------------
Days: Mon,Wed,Thu,Sat
Mask: 0b01011010

参考:ISO 8601, ref ISO 8601, Wiki ISO 8601