【问题标题】:perl fork() doesn't seem to utilize the cores, but cpu onlyperl fork() 似乎没有使用内核,但仅使用 cpu
【发布时间】:2017-03-22 20:27:54
【问题描述】:

我有一个 perl 程序,它试图将一堆文件从一种格式转换为另一种格式(通过命令行工具)。它工作正常,但太慢了,因为它一次又一次地转换文件。

我研究并利用了 fork() 机制,试图将所有转换作为子叉子产生,希望利用 cpu/cores。

编码已经完成并经过测试,它确实提高了性能,但并没有达到我的预期。 在查看 /proc/cpuinfo 时,我有这个:

> egrep -e "core id" -e ^physical /proc/cpuinfo|xargs -l2 echo|sort -u
physical id : 0 core id : 0
physical id : 0 core id : 1
physical id : 0 core id : 2
physical id : 0 core id : 3
physical id : 1 core id : 0
physical id : 1 core id : 1
physical id : 1 core id : 2
physical id : 1 core id : 3

这意味着我有 2 个 CPU 和每个四核?如果是这样,我应该能够分出 8 个分叉,并且假设我应该能够完成 8 分钟的工作(每个文件 1 分钟,8 个文件)以在 1 分钟内完成(8 个分叉,每个分叉 1 个文件)。

但是,当我测试运行它时,仍然需要 4 分钟才能完成。它似乎只使用了 2 个 CPU,但没有使用内核?

因此,我的问题是:

  1. perl 的 fork() 是否仅基于 CPU 而不是内核并行它?或者也许我没有做对?我只是使用 fork() 和 wait()。没什么特别的。

  2. 我假设 perl 的 fork() 应该使用内核,我可以编写一个简单的 bash/perl 来证明我的操作系统(即 RedHat 4)还是 Perl 不是这种症状的罪魁祸首?

添加:

我什至尝试多次运行以下命令来模拟多次处理和监控 htop。

while true; do echo abc >>devnull; done &

不知何故 htop 告诉我我有 16 个内核?然后当我生成 4 个上述 while 循环时,我看到其中 4 个每个使用 ~100% cpu。当我产生更多时,它们都开始均匀地降低 CPU 利用率。 (例如 8 个处理,参见 htop 中的 8 个 bash,但每个使用 ~50%)这是否意味着什么?

先谢谢了。我尝试了谷歌但无法找到明显的答案。


编辑:2016-11-09

这里是 perl 代码的摘录。我很想看看我在这里做错了什么。

my $maxForks = 50;
my $forks = 0;
while(<CIFLIST>) {
    extractPDFByCIF($cifNumFromIndex, $acctTypeFromIndex, $startDate, $endDate);
}
for (1 .. $forks) {
    my $pid = wait();
    print "Child fork exited.  PID=$pid\n";
}

sub extractPDFByCIF {
    # doing SQL constructing to for the $stmt to do a DB query
    $stmt->execute();

    while ($stmt->fetch()) {
        # fork the copy/afp2web process into child process
        if ($forks >= $maxForks) {
            my $pid = wait();
            print "PARENTFORK: Child fork exited.  PID=$pid\n";
            $forks--;
        }
        my $pid = fork;
        if (not defined $pid) {
            warn "PARENTFORK: Could not fork.  Do it sequentially with parent thread\n";
        }
        if ($pid) {
            $forks++;
            print "PARENTFORK: Spawned child fork number $forks. PID=$pid\n";
        }else {
            print "CHILDFORK: Processing child fork. PID=$$\n";
            # prevent child fork to destroy dbh from parent thread
            $dbh->{InactiveDestroy} = 1;
            undef $dbh;

            # perform the conversion as usual
            if($fileName =~ m/.afp/){
                    system("file-conversion -parameter-list");
            } elsif($fileName =~ m/.pdf/) {
                    system("cp $from-file $to-file");
            } else {
                    print ERRORLOG "Problem happened here\r\n";
            }
            exit;
        }
        # end forking

    $stmt->finish();
    close(INDEX);
}

【问题讨论】:

  • Perl 只使用fork 系统调用。您应该在 C 中看到完全相同的行为。
  • 不看代码很难调试。
  • 您的代码可能有问题;展示它,我们可以提供帮助。
  • 节目有点晚了,但是由于您有代码而我没有看到它,所以我想我会更新答案。总结是 - 我猜“文件转换”是磁盘 io 限制,而不是 CPU 限制。 cp 当然是。

标签: multithreading perl fork


【解决方案1】:

fork() 产生一个新进程 - 与现有进程相同,并具有与现有进程相同的状态。不多也不少。内核调度它并在任何地方运行它。

如果你没有得到你期望的结果,我建议一个更可能的限制因素是你正在从你的磁盘子系统读取文件 - 磁盘很慢,并且竞争 IO 实际上并没有使它们更快 - 如果有任何相反的情况,因为它会强制进行额外的驱动器搜索和不太容易的缓存。

具体来说:

1/ 不,fork() 只是克隆您的进程。

2/ 除非您想将大部分算法重写为 shell 脚本,否则基本上没有意义。没有真正的理由认为它会有所不同。

继续您的编辑:

  • system('file-conversion') 看起来非常像基于 IO 的进程,它会受到磁盘 IO 的限制。你的cp 也一样。

  • 您是否考虑过Parallel::ForkManager,它极大地简化了分叉位?

  • 作为一个较小的样式点,您可能应该使用 3 arg 'open'。

    #!/usr/bin/env perl
    use strict;
    use warnings;
    use Parallel::ForkManager;
    
    my $maxForks = 50;
    
    my $manager = Parallel::ForkManager->new($maxForks);
    
    while ($ciflist) {
    
        ## do something with $_ to parse.
    
        ##instead of: extractPDFByCIF($cifNumFromIndex, $acctTypeFromIndex, $startDate, $endDate);
    
        # doing SQL constructing to for the $stmt to do a DB query
        $stmt->execute();
    
        while ( $stmt->fetch() ) {
    
            # fork the copy/afp2web process into child process
            $manager->start and next;
            print "CHILDFORK: Processing child fork. PID=$$\n";
    
            # prevent child fork to destroy dbh from parent thread
            $dbh->{InactiveDestroy} = 1;
            undef $dbh;
    
            # perform the conversion as usual
            if ( $fileName =~ m/.afp/ ) {
                system("file-conversion -parameter-list");
            } elsif ( $fileName =~ m/.pdf/ ) {
                system("cp $from-file $to-file");
            } else {
                print ERRORLOG "Problem happened here\r\n";
            }
    
            # end forking
            $manager->finish;
        }
        $stmt->finish();
    
    }
    
    $manager->wait_all_children;
    

【讨论】:

    【解决方案2】:

    您的目标是以使用多个内核作为独立资源的方式并行化您的应用程序。您想要实现的是多线程,特别是 Perl 的 ithreads ,它们使用对底层系统的 fork() 函数的调用(并且是 heavy-weight 因此) 。您可以从perlthrtut 向自己教授 Perl 的多线程方式。引用perlthrtut:

    当一个新的 Perl 线程被创建时,与当前线程相关的所有数据都被复制到新线程,并且随后是该新线程私有的!这在感觉上类似于 Unix 进程分叉时发生的情况,只是在这种情况下,数据只是被复制到同一进程中内存的不同部分,而不是发生真正的分叉。

    话虽如此,关于您的问题:

    1. 你做得不对(抱歉)。 [请看我的评论...] 使用多线程,你不需要调用fork() 由您自己完成,但 Perl 会为您完成。

    2. 您可以检查您的 Perl 解释器是否已经构建了线程支持,例如通过perl -V(注意大写V)并查看消息。如果没有关于 threads 的内容,那么您的 Perl 解释器就不能支持 Perl 多线程。

    使用fork(),即使只有一个 CPU 内核,您的应用程序已经更快的原因很可能是当一个进程必须等待慢速资源(例如文件系统)时,另一个进程可以使用相同的内核作为计算资源同时。

    【讨论】:

    • 不是我的反对意见,而是一些perl 程序员不喜欢线程。
    • 不是我的反对意见,但线程与 fork 没有任何关系(可能除了在 Windows 上它使用最初作为 Windows 的 fork 的模拟)
    • @ysth ...哇,你是对的,我错了,谢谢!我再次查看perlthrtut 并相应地编辑了我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-31
    • 2013-10-27
    • 1970-01-01
    • 1970-01-01
    • 2012-08-20
    相关资源
    最近更新 更多