【问题标题】:Shared readonly memory between Perl processesPerl 进程之间共享只读内存
【发布时间】:2024-04-29 16:50:02
【问题描述】:

我想让我的 Perl 程序使用多核。它逐步读取查询输入,并将其中的块与每次运行时从文件加载到内存中的只读数据结构进行比较。该数据结构通常为几千兆字节,是一小组用于小型 C 例程的打包字符串。当进程分叉时,所有内容都会被复制,这在多核机器上会迅速耗尽 RAM。我尝试了几个非标准模块,但都导致运行缓慢和/或损坏 RAM。我认为,对于只读数据,Perl 不会坚持制作副本。其他语言可以做到。有人有想法吗?

【问题讨论】:

  • 这个讨论看起来很有趣:*.com/questions/9733146/… 特别是有人建议安装一些 RAM 作为硬盘驱动器,然后从那里使用文件 I/O。那会解决你的问题。问题是:值得吗?
  • 谢谢,使用文件是一种选择。但我认为孩子们会如此旋转磁盘,以至于多核速度变得比单核速度更差,所有这些都在内存中。我这么说是因为我有一个使用文件的程序的早期版本,它慢了 20 倍。其中一个模块使用 Sockets 使内存“共享”,但这太慢了很多。
  • 请注意,dan1111 并不建议您使用实际文件。磁盘旋转不会进入此解决方案。
  • 啊,是的,我看错了。但是制作 ramfs 是特定于操作系统的,我也希望程序以普通用户身份运行。我可以试试 Sys::Mmap。
  • “炸毁内存”是什么意思?我在 Google 上找不到与该短语相关的任何内容。

标签: perl memory process shared readonly


【解决方案1】:

Fork 通常不会复制内存,直到它被修改(搜索写时复制或 COW)。您确定您正在正确测量内存使用情况吗?从 free 中减去之前/之后的值,而不是使用 top。

编辑 - 示例脚本

尝试使用以下设置运行以下命令: ./fork_mem_usage 5 10000 ./fork_mem_usage 25 10000 ./fork_mem_usage 5 100000 ./fork_mem_usage 25 100000

如果第一个增量大于后续增量,则 fork 使用写时复制。几乎可以肯定是(当然 Windows 除外)。

#!/usr/bin/perl
use strict;
use warnings;

my $num_kids  = shift @ARGV;
my $arr_size  = shift @ARGV;
print "$num_kids x $arr_size\n";

my @big_array = ('abcdefg') x $arr_size;
die "Array wrong length" unless ($arr_size == @big_array);

print_mem_usage('Start');

for my $i (1..$num_kids) {
    my $pid = fork();
    if ($pid) {
        if ($i % 5 == 0) {
            print_mem_usage($i);
        }
    }
    else {
        sleep(5);
        exit;
    }
}

print_mem_usage('End');
exit;

sub print_mem_usage {
    my $msg = shift;
    print "$msg: ";
    system q(free -m | grep buffers/cache | awk '{print $3}');
}

【讨论】:

  • 是的,我收到“内存不足”错误,已制作副本。欢迎提出如何避免这些建议。我尽我所能。
  • 感谢您的豪华回复。 ./fork_mem_usage 25 100000 给出:25 x 100000 开始:320 5:322 10:322 15:322 20:323 25:323 结束:323
  • 所以第一个使用〜2MB,其余的每个不到1。无论您遇到什么问题,都不是 fork 复制只读内存。
  • 确实,你是.. 在我的旧联想笔记本电脑上,总共有 2 GB 内存,可以启动任意数量的 600 mb 孩子。我将您的测试更改为字符串哈希,这更类似于我的数据结构,并在子项中采用随机子字符串。我很感激你把它放在我的眼皮底下,我确定我测试过普通的前叉,但一定是犯了一个错误。我将在我的石头上写上“从未正确检查过的人”。
【解决方案2】:

编辑和总结:

关于threads::shared 是一个选项,我大错特错。在创建线程时,甚至共享数据结构也会被复制。这确实很糟糕,因此我可以总结出 Perl 完全无法进行内存密集型计算。


当一个进程forks 时,内核复制整个进程。驻留在 RAM 中的所有内容都是重复的。没有任何语言可以解决这个问题。不过,你可以试试内存映射,也可以使用线程。

Perl 线程是fork 模拟,但您可以将变量声明为线程之间共享:

use threads;
use threads::shared;

my $sharedVariable :shared = 0;

my @worker;

for my $i (1 .. 6) {
   push @worker, threads->create(\&worker_sub);
}

$_->join() foreach @worker;

sub worker_sub {
   sleep rand 5;
   print $sharedVariable, "\n";
}

如果$sharedVariable 在一个线程中更新,则更改也会传播到其他线程。如果将 print 语句替换为

,则可以看到这一点
print threads->tid, "-->", ++$sharedVariable, "\n";

【讨论】:

  • 据我了解,线程不会比标准 Perl 程序更好地使用多核——正是因为它们是仿真而不是真实的东西。不过,如果我错了,请纠正我。
  • @dan1111 如果您愿意,请在多个线程中运行$i += 1 while 1 循环 5 秒钟,然后查看您的 CPU 使用率;-) 当操作系统使线程可用时,Perl 将使用它们。 Perl 可以在我的 Linux 系统上使用多个内核。
  • 谢谢 .. 是的,我尝试了 forks 模块,据说它在 RAM 上比线程更精简,但这增加了速度,并且它看起来(与顶部)好像内存被复制了。其他语言确实提供了零拷贝方式,例如 C++ Boost 库。我尝试了 Parallel::ForkManager、IPC::Shareable 和 IPC::ShareLite,但它们似乎都在复制。我可以将数据结构保留在文件中,但是孩子们会一直在寻找和寻找,我不认为程序会更快。除了另一种语言或新逻辑,不知道该做什么。
  • @NielsLarsen forks 模块在没有线程的情况下模拟旧 perls 的线程。它实际上具有与threads 相同的API。 (您使用的是什么 perl 版本?)ForkManager 使用具有序列化数据结构的文件进行数据共享——这不是您想要的。 Shareable 在内存中做同样的事情,所以全局有一个序列化数据结构,每个进程有一个反序列化数据结构。 threads::shared 真的应该解决你的问题。整个问题的一部分是 Perl 不像 C 那样对原始内存进行操作,而是通过垃圾收集为您管理内存。
  • @dan1111 澄清一下,Perl 线程可以有效地使用多个内核;它们是真正的操作系统级线程。他们只是有很多其他严重的问题,使他们使用起来不愉快。
【解决方案3】:

您可以使用 Cache::FastMmap 来存储共享数据。听说有人用这个做IPC而不是缓存,这个缓存是进程间共享的。其中大部分是用 C 编写的。不要忘记在初始化时添加 'raw_values=1'。可以压缩缓存中的值,因此如果您有足够的 CPU 并且您的数据可压缩,它将为您节省大量内存。

速度很快,这里有一些基准测试:http://cpan.robm.fastmail.fm/cache_perf.html

因为 Cache::FastMmap mmap 是一个共享文件到您的进程内存空间中,这会使每个进程看起来很大,即使它只是在使用缓存的所有进程之间共享的 mmap 内存,甚至可能被交换如果缓存使用率低,则退出。

但是,操作系统会认为您的进程非常大,这可能意味着您遇到了一些您之前设置的 BSD::Resource 或“ulimit”,您认为这些是正常的,但现在不再适用了,因此请注意。

【讨论】:

  • 是的,我也遇到了 Cache::FastMmap。但那是为了存储许多键/值对,其中值不能大于内存页......我的数据是一些不能拆分的长数组。对于键/值存储,我会使用京都内阁,它也是内存映射的,效率也更高。除非有大师过来说别的,否则就需要重新思考逻辑或另一种语言。