在Perl中,我如何释放内存给操作系统?

13

我在Perl中遇到了一些内存问题。当我填充一个大的哈希表时,无法将内存释放回操作系统。如果我用标量做同样的操作并使用undef,则它会将内存还给操作系统。

以下是我编写的测试程序。

#!/usr/bin/perl
###### Memory test
######

## Use Commands
use Number::Bytes::Human qw(format_bytes);
use Data::Dumper;
use Devel::Size qw(size total_size);

## Create Varable
my $share_var;
my %share_hash;
my $type_hash = 1;
my $type_scalar = 1;

## Start Main Loop
while (true) {
    &Memory_Check();
    print "Hit Enter (add to memory): "; <>;
    &Up_Mem(100_000);
    &Memory_Check();

    print "Hit Enter (Set Varable to nothing): "; <>;
    $share_var = "";
    $share_hash = ();
    &Memory_Check();

    print "Hit Enter (clean data): "; <>;
    &Clean_Data();
    &Memory_Check();

    print "Hit Enter (start over): "; <>;
}

exit;


#### Up Memory
sub Up_Mem {
    my $total_loops = shift;
    my $n = 1;
    print "Adding data to shared varable $total_loops times\n";

    until ($n > $total_loops) {
        if ($type_hash) {
            $share_hash{$n} = 'X' x 1111;
        }
        if ($type_scalar) {
            $share_var .= 'X' x 1111;
        }
        $n += 1;
    }
    print "Done Adding Data\n";
}

#### Clean up Data
sub Clean_Data {
    print "Clean Up Data\n";

    if ($type_hash) {
        ## Method to fix hash (Trying Everything i can think of!
        my $n = 1;
        my $total_loops = 100_000;
        until ($n > $total_loops) {
            undef $share_hash{$n};
            $n += 1;
        }

        %share_hash = ();
        $share_hash = ();
        undef $share_hash;
        undef %share_hash;
    }
    if ($type_scalar) {
        undef $share_var;
    }
}

#### Check Memory Usage
sub Memory_Check {
    ## Get current memory from shell
    my @mem = `ps aux | grep \"$$\"`;
    my($results) = grep !/grep/, @mem;

    ## Parse Data from Shell
    chomp $results;
    $results =~ s/^\w*\s*\d*\s*\d*\.\d*\s*\d*\.\d*\s*//g; $results =~ s/pts.*//g;
    my ($vsz,$rss) = split(/\s+/,$results);

    ## Format Numbers to Human Readable
    my $h = Number::Bytes::Human->new();
    my $virt = $h->format($vsz);
    my $h = Number::Bytes::Human->new();
    my $res = $h->format($rss);

    print "Current Memory Usage: Virt: $virt  RES: $res\n";

    if ($type_hash) {
        my $total_size = total_size(\%share_hash);
        my @arr_c = keys %share_hash;
        print "Length of Hash: " . ($#arr_c + 1) . "  Hash Mem Total Size: $total_size\n";
    }
    if ($type_scalar) {
        my $total_size = total_size($share_var);
        print "Length of Scalar: " . length($share_var) . "  Scalar Mem Total Size: $total_size\n";
    }

}

输出:

./Memory_Undef_Simple.cgi 
当前内存使用情况:虚拟内存:6.9K  实际内存:2.7K
哈希长度:0  哈希内存总大小:92
标量长度:0  标量内存总大小:12
按 Enter 键(添加到内存):
向共享变量添加数据 100000 次
完成添加数据
当前内存使用情况:虚拟内存:228K  实际内存:224K
哈希长度:100000  哈希内存总大小:116813243
标量长度:111100000  标量内存总大小:111100028
按 Enter 键(将变量设置为空):
当前内存使用情况:虚拟内存:228K  实际内存:224K
哈希长度:100000  哈希内存总大小:116813243
标量长度:0  标量内存总大小:111100028
按 Enter 键(清除数据):
清除数据
当前内存使用情况:虚拟内存:139K  实际内存:135K
哈希长度:0  哈希内存总大小:92
标量长度:0  标量内存总大小:24
按 Enter 键(重新开始):

你可以看到内存下降了,但它只降低了标量的大小。有什么办法可以释放哈希表的内存吗?

此外,Devel::Size 显示哈希表只使用了 92 字节,尽管程序仍在使用 139K。


你需要重新格式化你的帖子,它无法阅读。 - EightyEight
我可以向您保证,Perl并不仅仅使用2.7K的内存。 ps以1K块报告内存,您的内存使用量低了1024倍。 - Schwern
参见:https://www.effectiveperlprogramming.com/2018/09/undef-a-scalar-to-release-its-memory/,了解如何在Perl池中管理内存的良好讨论。 - lordadmira
4个回答

19

一般来说,是的,这就是UNIX上的内存管理方式。如果你使用Linux和最新的glibc,并且使用了那个malloc函数,你可以将已释放的内存返回给操作系统。但我不确定Perl是否这样做。

如果你想处理大型数据集,请不要将整个数据集加载到内存中,可以使用类似BerkeleyDB的东西:

https://metacpan.org/pod/BerkeleyDB

以下是摘抄的示例代码:

  use strict ;
  use BerkeleyDB ;

  my $filename = "fruit" ;
  unlink $filename ;
  tie my %h, "BerkeleyDB::Hash",
              -Filename => $filename,
              -Flags    => DB_CREATE
      or die "Cannot open file $filename: $! $BerkeleyDB::Error\n" ;

  # Add a few key/value pairs to the file
  $h{apple}  = "red" ;
  $h{orange} = "orange" ;
  $h{banana} = "yellow" ;
  $h{tomato} = "red" ;

  # Check for existence of a key
  print "Banana Exists\n\n" if $h{banana} ;

  # Delete a key/value pair.
  delete $h{apple} ;

  # print the contents of the file
  while (my ($k, $v) = each %h)
    { print "$k -> $v\n" }

  untie %h ;

(好的,不是逐字翻译。他们使用use vars有点过时...)

你可以用这种方式在哈希表中存储数千兆字节的数据,而只使用非常少的内存。(基本上,BDB页面决定保留在内存中的内容;这是可控的。)


1
+1 非常好的演示了FAQ答案最后一部分中给出的建议:http://faq.perl.org/perlfaq3.html#How_can_I_make_my_Pe1 - Sinan Ünür
3
常见问题解答中的关于性能的内容是错误的,通常情况下会命中缓存,这与访问内存结构在时间上的成本相比并不更高。一旦开始交换,内存结构就会变得非常慢,因为哈希表没有很好的引用局部性。我记得写过一些ETL脚本,使用绑定BDB哈希表而不是原生哈希表可以使其运行速度提高几个数量级。 - jrockway
@jrockway 我猜性能惩罚只有在你不担心内存使用时才会有影响:小型数据结构完全适合轻负载机器的内存。 - Sinan Ünür
1
是的,不要用这个来解析你的三行 /etc/passwd 或其他什么东西。(当然,你可能不会注意到三条记录的速度缓慢 :)) - jrockway
我将以下代码添加到您的代码末尾:$SIG{CHLD} = 'IGNORE'; unless (my $pid = fork) { print "$h{banana}\n"; $h{banana} = 'YELLOW'; print "$h{banana}\n"; exit; }$SIG{CHLD} = 'IGNORE'; unless (my $pid = fork) { sleep 2; print "$h{banana}\n"; exit; }sleep 3;哈希表更新在不同的进程中无法看到。有没有办法强制跨进程更新? - clintonm9
那时,你应该只使用原始的BDB接口。 "tie"接口仅适用于快速脚本。 - jrockway

12

一般而言,您不能指望Perl将内存释放给操作系统。

请参阅常见问题解答:如何释放数组或哈希以使程序缩小?

通常情况下,无法回收或重用分配给词法作用域(例如my()变量)的内存,即使它们超出范围也是如此。该内存被保留在变量再次进入范围时使用。可以通过undef()和/或delete()来重用分配给全局变量的内存(在程序内部)。

在大多数操作系统上,分配给程序的内存永远不能返回到系统。这就是为什么长时间运行的程序有时会重新执行自己的原因。一些操作系统(尤其是使用mmap(2)为分配大块内存的系统)可以回收不再使用的内存,但在这些系统上,必须配置和编译perl以使用操作系统的malloc,而不是perl自己的malloc

在浪费时间之前,阅读计算机中安装的常见问题解答列表总是一个好主意。

例如,如何减少Perl程序的内存占用?可能与您的问题相关。


2
你的RTFF链接非常好。它指出这是与操作系统相关的。如果你的操作系统支持,你可以将内存释放回操作系统。我已经使用ActivePerl在WinXP上编写了完全满足OP需求的代码。没有必要增加敌意,请考虑修复你的第一段。 - daotoad
我稍微缓和了这个冒犯性的问题。我们需要像你这样声誉超过10k的人!请不要冒这样的险。 - innaM
1
@daotoad和Manni,这是一个时间问题。当我写下这段话时,原始帖子格式混乱,我只能辨认出最开始的几行。请参考上面EightyEight的评论。无论如何,感谢你们的关注。 - Sinan Ünür

10

你为什么希望Perl释放内存给操作系统?你可以使用更大的交换空间。

如果实在必须这样做,请在一个分离的进程中完成任务,然后退出。


4
这个回答不应该被踩。在长时间运行的程序中,分叉进程是处理内存使用暂时激增的完全合理的方式。 - Sinan Ünür
问题是,服务器只有3GB的内存。1GB用于操作系统,1GB用于MySQL。我的进程将从27MB开始,并将增加到约800MB。然后系统将开始进入交换空间并减慢所有操作。fork的问题在于它会将所有800MB复制到新进程中。 - clintonm9
每秒钟不断从Mysql中选择数据似乎要慢得多。我需要尽可能接近实时的效果。你觉得这样做有什么问题吗? - clintonm9
2
@clintonm9:哎呀。你知道共享数据实际上在每个线程中都有一份副本吗?尝试将数据存储在绑定的数据库或SQLite中,看看是否足够快满足你的需求。如果不行,你就得从头开始重新考虑了。 - ysth
3
请问需要翻译的是:@clintonm9: also, compare the cost of your time working on this to the cost of additional memory (or even an additional server)... - ysth
显示剩余2条评论

1

尝试使用选项-Uusemymalloc重新编译perl,以使用系统的malloc和free。您可能会看到一些不同的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接