有没有办法在 Perl 5 中实现静态定义的关联数组?

Is there a way to implement statically-defined associative arrays in Perl 5?

如果我有一些带有(绝对)静态键集的散列,我可以避免在 运行 时间每次通过键访问该散列的元素时计算散列函数吗?

说,我有一个静态关联数组 %saa,其中的键是:

my %saa = (
 A => "aaa",
 B => "bbb",
 C => "ccc"
);

如何在编译时预先计算这些键的散列函数值,并在 运行 时间内以我对简单列表元素使用索引访问时所做的有效方式使用它。

据我所知,在 perl5 中,它可能仅作为使用列表的哈希模拟来实现 (use constant A=>0, B=>1...; $saa[A]="ddd")。

也许有更直接的方法在 Perl 中实现静态关联数组(也称为 "named tuples",而不是散列)?

例如,这就是所需的功能 implemented in Crystal language

因为这是 Perl,有很多方法至少可以做你想做的事。这是一个 tied hash 不执行任何哈希查找的实现:

sub DrvtinyStaticHash::TIEHASH {
    my ($pkg, $A, $B, $C) = @_;
    bless [ $A, $B, $C ], $pkg;
}

sub DrvtinyStaticHash::STORE {
    my ($self,$key,$value) = @_;
    if ($key eq 'A')      { $self->[0] = $value }
    elsif ($key eq 'B') { $self->[1] = $value }
    elsif ($key eq 'C') { $self->[2] = $value; }
    else { die "DrvtinyStaticHash: Bad key: $key" }
}

sub DrvtinyStaticHash::FETCH {
    my ($self,$key) = @_;
    return $self->[0] if $key eq 'A';
    return $self->[1] if $key eq 'B';
    return $self->[2] if $key eq 'C';
    die "DrvtinyStaticHash: Bad key: $key";
}

tie my %saa, 'DrvtinyStaticHash', 'aaa', 'bbb', 'ccc';

print $saa{"B"};     # "bbb"
$saa{"C"} = 42;
print $saa{"D"};     # error: Bad key

您可以将此推广到任何包名称和任何一组具有不健康 eval 调用的密钥。

最接近 struct NamedTuple(**T) is Const::Fast 中描述的功能。

#!/usr/bin/env perl

use strict;
use warnings;

use Const::Fast qw( const );

my %language;

BEGIN {
    const %language => (
        name =>'Crystal',
        year => '2011',
    );
}

print "$language{$_}\n" for qw( name year other );
$ perl t.pl
Crystal
2011
Attempt to access disallowed key 'other' in a restricted hash at t.pl line 17.

但是 检查发生在 运行 时间。有关性能信息,请参阅 Neil Bowers' excellent review。从那以后统计数据可能发生了变化,但它应该给你一个想法。

另一种方法是根本不使用散列,而是使用 Class::XSAccessor 来定义一个简单的 class:

#!/usr/bin/env perl

use strict;
use warnings;

package My::Language;
use Class::XSAccessor (
    constructor => 'new',
    getters => {
        name => 'name',
        year => 'year',
    },
);

package main;

my $language;

BEGIN {
    $language = My::Language->new(
        name =>'Crystal',
        year => '2011',
    );
}

print $language->$_, "\n" for qw( name year other );

Class::XSAccessor::Array 应该快大约 10%-15%:

#!/usr/bin/env perl

use strict;
use warnings;

package My::Language;
use Class::XSAccessor::Array (
    getters => {
        name => 0,
        year => 1,
    },
);

package main;

my $language;

BEGIN {
    $language = bless ['Crystal', '2011'] => 'My::Language';
}

print $language->$_, "\n" for qw( name year other );

在所有情况下,检查发生在 运行 时间。

Perl 已经预先计算了常量键的散列。然后在运行时检索哈希代码。此外,哈希键是constant-pooled,这样即使哈希冲突,字符串相等性也被简化为指针比较。

因此,与常量数组索引查找相比,常量哈希键查找的额外运行时成本非常低,尤其是与因 Perl 数据模型而产生的所有开销相比。

这是如何工作的?

在优化阶段,op.c 中的 S_maybe_multideref() 函数将 $x{foo}[0]{bar} 等连续的取消引用操作折叠成单个操作码。常量键的标量被放在一边,以便它们只是一个指针查找。这些标量由 S_check_hash_fields_and_hekify() 函数准备,该函数使用 newSVpvn_share() 函数创建 constant-pooled 标量,该函数(在几级宏之后)使用 S_share_hek_flags() 中的函数 hv.c 它在全局常量池(它本身就是一个哈希 table)中创建或获取哈希条目密钥 (HEK) 结构。

在运行时,哈希查找函数注意到键标量实际上是一个共享的 HEK,并直接检索哈希。 short-circuits 大部分散列 table 查找代码在 Perl_hv_common() 函数中。

这仅在哈希键使用常量字符串时有效。否则,您仍然必须进行完整的哈希查找。

因此,使用 $struct[KEY_FOO] 等常量数组键代替常量哈希键 $hash{foo} 不会给您带来明显的性能优势。相反,数组方法的主要好处是您必须预先 声明 所有常量,从而为您提供一些静态检查。这可以例如检测拼写错误。请注意,哈希还支持一些检查:

  • 使用fields编译指示,您可以静态限制可以通过变量访问的散列键。例如。如果 SomeClass class 声明了 foo 字段,my SomeClass $object 只允许 $object->{foo}。然而,这是 Perl 的一个非常神秘、误导和令人沮丧的特性。请不要使用它。

  • 使用“受限哈希”,您可以冻结哈希的键 table(参见 Hash::Util 中的 lock_keys())。这是一个 运行时 检查。如果您想获得 typo-detection 的好处,这是最好的方法。例如。我们可以声明一种结构,如:

    use Hash::Util qw(lock_keys);
    my %struct;
    lock_keys(%struct, qw(foo bar));
    
    $struct{foo};  # OK
    $struct{bar};  # OK
    $struct{qux};  # runtime error