有没有办法在 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
如果我有一些带有(绝对)静态键集的散列,我可以避免在 运行 时间每次通过键访问该散列的元素时计算散列函数吗?
说,我有一个静态关联数组 %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