代码之家  ›  专栏  ›  技术社区  ›  Peter V. Mørch

自定义可存储挂钩,用于隔离引用重对象的轻对象

  •  2
  • Peter V. Mørch  · 技术社区  · 7 年前

    package Tiny;
    
    sub new {
        my ($class, $tiny, $large) = @_;
        return bless { tiny => $tiny, large => $large };
    }
    

    我想创建一个 STORABLE_freeze / STORABLE_thaw 让我(递归地)克隆 $tiny 但要保持引用 $large

    我试过暂时删除 $self->{large} (见下文),并将其与 Scalar::Util::refaddr $大 ,序列化 $self 可储存的解冻 当它超出范围时被删除,但是 一直在散列中泄漏内存,我需要一个全局类成员散列( %largeWeakRefs

    怎么可能用更干净的方式来做呢?

    下面是我的解决方案,使用散列暂时保存大ref:

    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;
    }
    

    1 回复  |  直到 7 年前
        1
  •  2
  •   ikegami Gilles Quénot    7 年前

    您可以添加引用计数。

    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}
    }
    

    SvREFCNT_inc 和改变 newRV_noinc newRV 以避免潜在的内存泄漏。


    为了避免可能的内存泄漏,永远不要在对象中存储“大”。

    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(@_);
       }
    }
    

    未经测试。