自定义可存储挂钩,用于 dclone 引用重量级对象的轻量级对象
Custom Storable hooks for dclone-ing a light-weight object referencing a heavy-weight object
假设我有一个小对象引用了一个大对象:
package Tiny;
sub new {
my ($class, $tiny, $large) = @_;
return bless { tiny => $tiny, large => $large };
}
我想创建一个 STORABLE_freeze
/STORABLE_thaw
对,让我(递归地)克隆 $tiny
但 maintain/keep 对 $large
的引用原样,也没有克隆 $large。
我尝试暂时删除 $self->{large}
(见下文),并将其放入具有 Scalar::Util::refaddr
键和对 $large
的弱引用的散列中,序列化 [=] 的其余部分19=],然后立即将(弱)引用放回原始对象和 STORABLE_thaw
中的克隆对象,但它是一团糟,并且在每个克隆上,弱引用 值 在超出范围时被删除,但 key 保留在哈希中永远泄漏内存,我需要一个全局 class 成员哈希(%largeWeakRefs
) 来保存临时 $large
引用。有味道。
如何以更简洁的方式执行此操作?
这是我使用散列临时保存大引用的解决方案:
package Tiny;
use Scalar::Util qw(refaddr weaken);
sub new {
my ( $class, $tiny, $large ) = @_;
return bless { tiny => $tiny, large => $large }, $class;
}
# Ugly temporary storage to hold $large refs from _freeze to _thaw...
my %largeWeakRefs;
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
my $large = delete local $self->{large};
my $refaddr = refaddr $large;
$largeWeakRefs{$refaddr} = $large;
weaken $largeWeakRefs{$refaddr};
my %restOfSelf = %$self;
$self->{large} = $large;
return $refaddr, \%restOfSelf;
}
sub STORABLE_thaw {
my ($self, $cloning, $refaddr, $restOfSelf) = @_;
%$self = %$restOfSelf;
$self->{large} = $largeWeakRefs{$refaddr};
return $self;
}
(是的,我知道,我的例子只处理克隆,而不是直接冻结和解冻)
您可以添加引用计数。
my %larges;
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
if ($cloning) {
my $large_key = pack('j', refaddr(self->{large}));
$larges{$large_key} //= [ $self->{large}, 0 ];
++$larges{$large_key}[1];
return ( $large_key, $self->{tiny} );
} else {
return ( "", $self->{tiny}, $self->{large} );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
if ($cloning) {
my $large_key = $serialized;
$self->{ tiny } = shift;
$self->{ large } = $larges{$large_key}[0];
--$larges{$large_key}[1]
or delete($larges{$large_key});
} else {
$self->{ tiny } = shift;
$self->{ large } = shift;
}
}
未测试。
如果克隆过程终止,您将发生内存泄漏。
或者,您可以通过以下方式避免对外部资源的需求:
use Inline C => <<'__EOS__';
IV get_numeric_ref(SV *sv) {
SvGETMAGIC(sv);
if (!SvROK(sv))
croak("Argument not a reference");
sv = MUTABLE_SV(SvRV(sv));
SvREFCNT_inc(sv);
return PTR2IV(sv); /* Despite its name, can be used to convert pointer to IV */
}
SV* get_perl_ref_from_numeric_ref(IV iv) {
SV* sv = PTR2IV(iv);
return newRV_noinc(sv);
}
__EOS__
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
if ($cloning) {
return ( pack('j', get_numeric_ref($self->{large})), $self->{tiny} );
} else {
return ( "", $self->{tiny}, $self->{large} );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
if ($cloning) {
$self->{ tiny } = shift;
$self->{ large } = get_perl_ref_from_numeric_ref(unpack('j', $serialized));
} else {
$self->{ tiny } = shift;
$self->{ large } = shift;
}
}
没有测试 STORABLE_freeze
和 STORABLE_thaw
,但使用以下代码测试了 C/XS 代码:
use strict;
use warnings;
use feature qw( say state );
use Cpanel::JSON::XS qw( );
sub _dump {
state $encoder = Cpanel::JSON::XS->new->canonical->allow_nonref;
return $encoder->encode($_[0]);
}
{
my %h = ( a => 4, b => 5 );
say _dump(\%h); # {"a":4,"b":5}
say sprintf "0x%x", \%h; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 1
my $i = get_numeric_ref(\%h);
say sprintf "0x%x", $i; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 2
my $ref = get_perl_ref_from_numeric_ref($i);
say sprintf "0x%x", $ref; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 2
say _dump($ref); # {"a":4,"b":5}
}
如果克隆进程终止,您将发生内存泄漏。我想在克隆过程中依靠 "large" 不会去任何地方是安全的,因此您可以删除 SvREFCNT_inc
并将 newRV_noinc
更改为 newRV
以避免潜在的内存泄漏。
为避免可能的内存泄漏,切勿在对象中存储 "large"。
my %larges;
sub new {
my $class = shift;
my $self = bless({}, $class);
return $self->_init(@_);
}
sub _init {
my ($self, $tiny, $large) = @_;
$self->{ tiny } = $tiny;
{
my $large_key = pack('j', refaddr($self));
$self->{ large_key } = $large_key;
$larges{ $large_key } = $large;
}
return $self;
}
sub DESTROY {
my ($self) = @_;
if (defined( my $large_key = $self->{ large_key } )) {
delete( $larges{ $large_key } );
}
}
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
if ($cloning) {
return ( $self->{large_key}, $self->{tiny} );
} else {
return ( "", $self->{tiny}, $larges{ $self->{large_key} } );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
if ($cloning) {
my ($tiny) = @_;
my $large_key = $serialized;
$self->_init($tiny, $larges{ $large_key });
} else {
$self->_init(@_);
}
}
未测试。
如果克隆进程终止,则不会发生内存泄漏。
假设我有一个小对象引用了一个大对象:
package Tiny;
sub new {
my ($class, $tiny, $large) = @_;
return bless { tiny => $tiny, large => $large };
}
我想创建一个 STORABLE_freeze
/STORABLE_thaw
对,让我(递归地)克隆 $tiny
但 maintain/keep 对 $large
的引用原样,也没有克隆 $large。
我尝试暂时删除 $self->{large}
(见下文),并将其放入具有 Scalar::Util::refaddr
键和对 $large
的弱引用的散列中,序列化 [=] 的其余部分19=],然后立即将(弱)引用放回原始对象和 STORABLE_thaw
中的克隆对象,但它是一团糟,并且在每个克隆上,弱引用 值 在超出范围时被删除,但 key 保留在哈希中永远泄漏内存,我需要一个全局 class 成员哈希(%largeWeakRefs
) 来保存临时 $large
引用。有味道。
如何以更简洁的方式执行此操作?
这是我使用散列临时保存大引用的解决方案:
package Tiny;
use Scalar::Util qw(refaddr weaken);
sub new {
my ( $class, $tiny, $large ) = @_;
return bless { tiny => $tiny, large => $large }, $class;
}
# Ugly temporary storage to hold $large refs from _freeze to _thaw...
my %largeWeakRefs;
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
my $large = delete local $self->{large};
my $refaddr = refaddr $large;
$largeWeakRefs{$refaddr} = $large;
weaken $largeWeakRefs{$refaddr};
my %restOfSelf = %$self;
$self->{large} = $large;
return $refaddr, \%restOfSelf;
}
sub STORABLE_thaw {
my ($self, $cloning, $refaddr, $restOfSelf) = @_;
%$self = %$restOfSelf;
$self->{large} = $largeWeakRefs{$refaddr};
return $self;
}
(是的,我知道,我的例子只处理克隆,而不是直接冻结和解冻)
您可以添加引用计数。
my %larges;
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
if ($cloning) {
my $large_key = pack('j', refaddr(self->{large}));
$larges{$large_key} //= [ $self->{large}, 0 ];
++$larges{$large_key}[1];
return ( $large_key, $self->{tiny} );
} else {
return ( "", $self->{tiny}, $self->{large} );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
if ($cloning) {
my $large_key = $serialized;
$self->{ tiny } = shift;
$self->{ large } = $larges{$large_key}[0];
--$larges{$large_key}[1]
or delete($larges{$large_key});
} else {
$self->{ tiny } = shift;
$self->{ large } = shift;
}
}
未测试。
如果克隆过程终止,您将发生内存泄漏。
或者,您可以通过以下方式避免对外部资源的需求:
use Inline C => <<'__EOS__';
IV get_numeric_ref(SV *sv) {
SvGETMAGIC(sv);
if (!SvROK(sv))
croak("Argument not a reference");
sv = MUTABLE_SV(SvRV(sv));
SvREFCNT_inc(sv);
return PTR2IV(sv); /* Despite its name, can be used to convert pointer to IV */
}
SV* get_perl_ref_from_numeric_ref(IV iv) {
SV* sv = PTR2IV(iv);
return newRV_noinc(sv);
}
__EOS__
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
if ($cloning) {
return ( pack('j', get_numeric_ref($self->{large})), $self->{tiny} );
} else {
return ( "", $self->{tiny}, $self->{large} );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
if ($cloning) {
$self->{ tiny } = shift;
$self->{ large } = get_perl_ref_from_numeric_ref(unpack('j', $serialized));
} else {
$self->{ tiny } = shift;
$self->{ large } = shift;
}
}
没有测试 STORABLE_freeze
和 STORABLE_thaw
,但使用以下代码测试了 C/XS 代码:
use strict;
use warnings;
use feature qw( say state );
use Cpanel::JSON::XS qw( );
sub _dump {
state $encoder = Cpanel::JSON::XS->new->canonical->allow_nonref;
return $encoder->encode($_[0]);
}
{
my %h = ( a => 4, b => 5 );
say _dump(\%h); # {"a":4,"b":5}
say sprintf "0x%x", \%h; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 1
my $i = get_numeric_ref(\%h);
say sprintf "0x%x", $i; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 2
my $ref = get_perl_ref_from_numeric_ref($i);
say sprintf "0x%x", $ref; # 0x32cdbf8
say Internals::SvREFCNT(%h); # 2
say _dump($ref); # {"a":4,"b":5}
}
如果克隆进程终止,您将发生内存泄漏。我想在克隆过程中依靠 "large" 不会去任何地方是安全的,因此您可以删除 SvREFCNT_inc
并将 newRV_noinc
更改为 newRV
以避免潜在的内存泄漏。
为避免可能的内存泄漏,切勿在对象中存储 "large"。
my %larges;
sub new {
my $class = shift;
my $self = bless({}, $class);
return $self->_init(@_);
}
sub _init {
my ($self, $tiny, $large) = @_;
$self->{ tiny } = $tiny;
{
my $large_key = pack('j', refaddr($self));
$self->{ large_key } = $large_key;
$larges{ $large_key } = $large;
}
return $self;
}
sub DESTROY {
my ($self) = @_;
if (defined( my $large_key = $self->{ large_key } )) {
delete( $larges{ $large_key } );
}
}
sub STORABLE_freeze {
my ( $self, $cloning ) = @_;
if ($cloning) {
return ( $self->{large_key}, $self->{tiny} );
} else {
return ( "", $self->{tiny}, $larges{ $self->{large_key} } );
}
}
sub STORABLE_thaw {
my ( $self, $cloning, $serialized ) = splice(@_, 0, 3);
if ($cloning) {
my ($tiny) = @_;
my $large_key = $serialized;
$self->_init($tiny, $larges{ $large_key });
} else {
$self->_init(@_);
}
}
未测试。
如果克隆进程终止,则不会发生内存泄漏。