Speed up calculation by running in parallel

2023-11-09

原文链接:https://perlmaven.com/speed-up-calculation-by-running-in-parallel

In this example we have a bunch of numbers. We need to make some heavy calculation on each number (using our calc function), and then collect the results in a hash where the keys are the original numbers and the values are the results.

For the sake of our example the “heavy computation” is generating and summing up lots of random numbers.

The linear solution

This is quite simple to do in linear code. We just need to iterate over the elements of the original array that holds the input numbers and call the calc function on each one of them. When the function is done it returns the result that we can then assign to the appropriate element in the hash.

examples/calc_no_fork.pl

use strict;
use warnings;
use Data::Dumper qw(Dumper);
 
my @numbers = map { $_ * 2000000 } reverse 1 .. 10;
my %results;
 
foreach my $q (@numbers) {
    $results{$q} = calc($q);
}
 
print Dumper \%results;
 
sub calc {
    my ($n) = @_;
    my $sum = 0;
    for (1 .. $n) {
        $sum += 1 + rand()/100;
    }
    return $sum;
}

(If it is unclear what is in the @numbers array and how it is generated then use the Dumper function to print the content of @numbers right after it was created and read about map in Perl.

We can run this code using the time program of the Unix/Linux shell:

$ time perl calc_no_fork.pl

The result on my computer was:

real    0m21.032s

It took 21 seconds to do all the calculations.

While the program was running I’ve also used the htop program in another console and saw that none of the 4 cores of my computer is fully used.

在这里插入图片描述
I think the reason that none of them is fully used is that the operating system moves the process around form CPU to CPU, but I am not 100% sure in this.

Using fork

In order to allow the computer to better utilize all its resources we could use either threads or fork. As threads are not a recommended technique in Perl we opt to use fork as described in the article on Using fork to spread load to multiple cores. The problem, as it is also mentioned at the end of that article is that forked processes don’t have shared memory, so the forked process cannot simple write back to the common %results hash. That’s where the module Parallel::ForkManager comes into play.

It is a wrapper around the regular fork function of Perl that provides us with various nice extra services. Including the possibility to return data from the child processes to the parent process that launched them.

examples/calc_fork_manager.pl

use strict;
use warnings;
use Parallel::ForkManager;
use Data::Dumper qw(Dumper);
 
my $forks = shift or die "Usage: $0 N\n";
 
my @numbers = map { $_ * 2000000 } reverse 1 .. 10;
my %results;
 
print "Forking up to $forks at a time\n";
my $pm = Parallel::ForkManager->new($forks);
 
$pm->run_on_finish( sub {
    my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_structure_reference) = @_;
    my $q = $data_structure_reference->{input};
    $results{$q} = $data_structure_reference->{result};
});
 
foreach my $q (@numbers) {
    my $pid = $pm->start and next;
    my $res = calc($q);
    $pm->finish(0, { result => $res, input => $q });
}
$pm->wait_all_children;
 
print Dumper \%results;
 
sub calc {
    my ($n) = @_;
    my $sum = 0;
    for (1 .. $n) {
        $sum += 1 + rand()/100;
    }
    return $sum;
}

The code starts with the creation of the Parallel::ForkManager object and setting the maximum number of parallel child processes.

my $pm = Parallel::ForkManager->new($forks);

Then we create an anonymous function (a sub without a name) and pass it to the run_on_finish method of Parallel::ForkManager. This function will be called once for each child process immediately as the child process terminates. It receives a number of parameters, but the one that is interesting to us now is the 6th, the last parameter which we assigned to the $data_structure_reference variable.

This variable will hold everything we sent back from the child process. In our case that will be a hash reference with two keys. “input” will contain the value from the original @numbers array the specific child process is dealing with. The “result” will contain the value returned by the calc() function.

$pm->run_on_finish( sub {
    my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_structure_reference) = @_;
    my $q = $data_structure_reference->{input};
    $results{$q} = $data_structure_reference->{result};
});

Then comes the main part of the code.

We have a simple foreach loop iterating over the @numbers array. For each iteration we call my $pid = $pm->start and next; This will try to create a new fork. If it is successful then at this point two processes will continue to run almost exactly the same way: the value returned by the start method is assigned to $pid. There is however a small difference in the two processes.

In the parent process, this value is going to be the process ID of the child process, a non-zero number, and therefore the right-hand side of the and boolean operator will be evaluated and the main process will go to the next iteration of the foreach loop.

In the child process the value returned by start will be 0. Which is false. Which means the right-hand side of the and operator will not be executed. In the child process the next evaluated statement will be the calc($q);. While the child process is calculating using one of the CPUs of the computer, the main process can run using the other CPU and it can create more child-processes.

The Parallel::Forkmanager will also count how many child processes have been forked and if we reach the value passed to the new constructor then the start command will wait till one of the earlier child-processes finishes and will only fork a new child-process after that.

In the meantime all the child processes are running on one of the CPUs of the computer. When one of them finishes the calc function it will call the finish method of Parallel::Forkmanager and it will pass to it two values. The first one is the exit code it wishes to have. 0 means success. The second one is a reference to a data structure it wishes to send back to the main process. This is the data structure we have used in the anonymous subroutine in $data_structure_reference.

foreach my $q (@numbers) {
    my $pid = $pm->start and next;
    my $res = calc($q);
    $pm->finish(0, { result => $res, input => $q });
}
$pm->wait_all_children;

The call to wait_all_children makes sure that the parent process will indeed wait till all the processes it created have finished and will only continue running once that happened and once it had a chance to run the respective run_on_finish function.

We can run this script as

time perl ~/work/perlmaven.com/examples/calc_fork_manager.pl 8

The result is about twice as fast as the linear version:

real    0m11.138s

At the same time htop shows that all the CPUs are saturated.
在这里插入图片描述
In some other measurements I’ve seen a 3-time speedup, but I think you can’t expect anything better with a 4-core machine. After all there are many other tasks running in the system, so we don’t really have 4 times more free CPU power than earlier, and the whole forking and managing the communication has some overhead.

Combined example

Just in case you’d like to tweak the calc() function and would like to further experiment with this code, I’ve included a version of the two scripts combined together. If you run it without any parameter it will run the linear version. If you run it with any positive number, it will use that many parallel child processes.

examples/calc_fork_manager_full.pl

use strict;
use warnings;
use Parallel::ForkManager;
use Data::Dumper qw(Dumper);
 
 
my $forks = shift;
 
my @numbers = map { $_ * 2000000 } reverse 1 .. 10;
my %results;
 
if ($forks) {
    print "Forking up to $forks at a time\n";
    my $pm = Parallel::ForkManager->new($forks);
    $pm->run_on_finish( sub {
        my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_structure_reference) = @_;
#die Dumper \@_;
        my $q = $data_structure_reference->{input};
        $results{$q} = $data_structure_reference->{result};
    });
 
    DATA_LOOP:
    foreach my $q (@numbers) {
        my $pid = $pm->start and next DATA_LOOP;
        my $res = calc($q);
        $pm->finish(0, { result => $res, input => $q });
    }
    $pm->wait_all_children;
} else {
    print "Non-forking\n";
    foreach my $q (@numbers) {
        $results{$q} = calc($q);
    }
}
 
print Dumper \%results;
 
sub calc {
    my ($n) = @_;
    my $sum = 0;
    for (1 .. $n) {
        $sum += 1 + rand()/100;
    }
    return $sum;
}

BTW, the “DATA_LOOP” in this example is not really needed, it only tries to make the code a bit more readable.
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Speed up calculation by running in parallel 的相关文章

  • 从具有多行记录的文件中提取数据,将每个范围保存到单独的文件中

    我有一个看起来像这样的文件 TRANSACTION STARTED 020t CARD INSERTED 020tCARD 5845 DATE 01 02 16 TIME 05 45 52 05 46 26 GENAC 1 ARQC EXT
  • fork() 是如何工作的?

    我对fork真的很陌生 这段代码中的pid在做什么 有人可以解释一下 X 行和 Y 行的结果吗 include
  • Mojolicious:我应该使用一个还是多个 websocket?

    我正在自学 Mojolicious 和 websockets 到目前为止 我已经有了一个网页 它显示数据库中的行 并具有用于添加 删除和更新行以及选择用于排序的列的按钮 目前 它在每个按钮的 javascript onclick 处理程序中
  • 如何从 Perl 中的文本文件中提取/解析表格数据?

    我正在寻找类似的东西HTML 表格提取 http search cpan org dist HTML TableExtract 只是不适用于 HTML 输入 而是适用于包含采用缩进和间距格式化的 表格 的纯文本输入 数据可能如下所示 Her
  • 如何在perl中使用O_ASYNC和fcntl?

    我想使用 O ASYNC 选项 当管道可以读取时 SIGIO 的处理程序将运行 但以下代码不起作用 任何人都可以帮助我吗 bin env perl use Fcntl SIG IO sub print catch SIGIO n my fl
  • 在哪里可以找到将现有 CPAN 模块转换为使用 Dist::Zilla 的简明指南?

    我曾多次阅读过相关文档和一些博客文章地区 齐拉 http search cpan org dist Dist Zilla 我从来没有对自己的理解充满信心 在回答另一个问题时 Ether 提出了转换的可能性地穴 SSLeay http sea
  • 使用perl创建层次结构文件

    我的任务是使用 perl 创建父子层次结构文件 示例输入文件 制表符分隔 记录将以随机顺序排列在文件中 父项 可能出现在 子项 之后 S5 S3 S5 S8 ROOT S1 S1 S7 S2 S5 S3 S4 S1 S2 S4 77 S2
  • 本地“关闭”binmode(STDOUT, ":utf8")

    我的脚本开头有以下块 usr bin perl5 w use strict binmode STDIN utf8 binmode STDOUT utf8 binmode STDERR utf8 在某些子程序中 当存在其他编码 来自远程子程序
  • 如何匹配单引号字符串中的双反斜杠?

    我需要区分带有单反斜杠和双反斜杠的字符串 Perl 对它们一视同仁 print n qqq www eee rrr print n qqq www eee rrr 将给出相同的结果 qqq www eee rrr qqq www eee r
  • 在 Perl 中如何打印到变量而不是文件?

    如何使用 Perl 打印到变量 我已经在一个程序上工作了一段时间 它记录了它的迭代的以非常详细的方式取得进展 print loghandle some message 但是 我还想有选择地将一些消息打印到不同的文件中 当然 我可以在代码中添
  • 关闭 python 后让进程保持运行

    我希望在终止原始进程后继续运行进程 以下代码在终止原始进程后不会使其进程保持运行 args yes Popen args shell True stdout None stdin None stderror None 我已经尝试了我能想到的
  • 当 sleep() 不能与闹钟配合使用时,我还能做什么“睡眠”?

    有许多文档说 您应该避免使用带有警报的睡眠 因为许多系统使用警报来实现睡眠 事实上 我正在遭受这个问题的困扰 那么 当 sleep 无法与闹钟配合使用时 有人可以帮助我 我还能做什么 睡眠 吗 我已经尝试过 Time HiRes 模块的 u
  • 使用“严格引用”时不能使用字符串作为 ARRAY 引用

    我正在尝试执行下面提到的代码并观察到错误 在使用 严格引用 时 无法使用字符串 RCSoWLAN ePDG 2 Qguest ASUS ATT 作为 ARRAY 引用 Perl代码 perl64 bin perl use strict us
  • 导入 .pl 文件

    我想知道如何将 Perl 文件导入到脚本中 我尝试了 use require 和 do 但似乎没有什么对我有用 这就是我用 require 做到的 usr bin perl require equations print x1 n 是否可以
  • 修改排序比较器内的字符串

    我有以下代码 在排序比较器中 它在进行比较之前删除前缀字符串 print for sort a s STRING b s STRING foo a cmp foo b a 尽管比较和顺序是正确的 但前缀字符串已从输出中删除 以下保留前缀字符
  • Perl 中的线程定时循环

    本质上 我希望有一个高优先级线程 它以给定的时间间隔 此处为 0 5 毫秒 运行并中断 一切 执行一个短任务 然后返回 睡眠 状态 使用 Ubuntu 11 04 和 perl v5 10 1 问题是 虽然我得到了某种结果 但我不确定是否有
  • 在 Perl 中将整个文件读入哈希值

    我在 Perl 中将文件读入哈希时遇到一些问题 Chr1 supercontig 000000000 1 500 PILOT21 588 1 3 14602 59349 1 Chr1 supercontig 000000001 5 100
  • 从 HoA 值中获取独特元素并打印

    我有一个 HoA 其中包含某些值 我只需要 HoA 中的独特元素 预期结果 Key 1 Element ABC DEF Key 2 Element XYZ RST Key 3 Element LMN 下面是我的脚本 usr bin perl
  • 我们应该聘请用 Perl 编写 C 的人吗? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我的一位同事最近面试了一些求职者 其中一位说他们有非常好的 Perl 经验 由于我的同事不懂 Perl 他要求我对那位潜在雇员编写的 场外 一些代码进
  • Perl:散列 2 中数组的数值排序(施瓦茨变换)

    这实际上是该线程的后续内容 Perl 散列中数组的数字排序 https stackoverflow com questions 7914931 perl numerical sort of arrays in a hash 我无法编辑原始问

随机推荐

  • 2020年研究生数学建模竞赛总结复盘

    文章目录 一 前言 二 赛题选择 三 做题思路 问题一 数据清洗 问题二 数据降维 问题三 建模预测 问题四 分析模型预测结果与实际值 问题五 可视化 四 总结 五 结果 三等奖 一 前言 今天是2020年研究生数学建模竞赛的最后一天 今早
  • git deamon 一个简单的git服务器

    git deamon 一个简单的git服务器 一 Git daemon 二 操作 三 参考 四 总结 一 Git daemon Git daemon是一个简单的git仓库服务器 可以用来共享局域网的本地仓库 二 操作 以下示例A电脑共享gi
  • 使用BindingList实现DataGridView的动态绑定

    在DataGridView数据绑定时使用BindingSource中转可以起到很好的控制DataGridView
  • 指针基础(2)【数组与指针】

    文章目录 写在前面 1 数组概述 1 1 一维数组 1 2 二维数组 1 3 多维数组 1 4 小结 2 C 中 vector 容器 2 1 定义和初始化 vector 对象 2 2 向 vector 对象中增加元素 2 3 vector
  • MQTT异常断开

    MQTT异常断开 讨论一下TCP链路的影响 MQTT异常断开 TCP链路原因的几种情况 三种情况 1 客户端发送了心跳请求 但是MQTT代理服务器Broker没有收到心跳请求 所以也不会回复客户端心跳响应 MQTT代理服务器Broker在
  • 前端 115道 面试题总结【持续更新...】

    前端面试题 1 说说你对useEffect的理解 可以模拟哪些生命周期 2 说说Real DOM和Virtual DOM的区别 优缺点 3 说说React中setState和replaceState的区别 4 说说React生命周期有哪些不
  • Web 开发中 20 个很有用的 CSS 库

    http www oschina net translate css libraries for developers
  • Android 开机加速优化

    文章目录 Android 开机加速优化 关闭BootLoader的企鹅 关闭开机动画Android 关闭锁屏 删除预装APP 查看预装APP 删除编译生成的APK 不编译APK 1 统一配置 2 修改单个APK的Android mk 谷歌A
  • 2022年5月计划(UE4视频教程+osgearth源码调试+ogreRenderSystem源码抄写)

    按照年度计划走就可以了 五一期间突击完了ue4第七套视频教程 客户端差不多了 各项终于达到什么都会 什么都不精了 没有短板 也没有长处 平衡进行就行了 包括久违的渲染 也可以引进了 以前如果单单干渲染是不行的 毕竟这种工作少 还要会引擎架构
  • springboot集成dubbo(详细配置)

    前言 首先要搭建zookeeper环境并启动 可参照window下搭建zookeeper 半生归来仍少年的博客 CSDN博客 dubbo管理平台搭建 下载 dubbo admin 2 5 8 war 互联网文档类资源 CSDN下载 放到to
  • 编译 java_如何编译java

    展开全部 用命令32313133353236313431303231363533e58685e5aeb931333337613139提示符编译java程序的步骤 1 先新建文本文档 输入自己的java程序 这里我写一个简单的java程序 来
  • 计算机网络arp表作用,arp映射表是什么?有什么作用

    在如今每一天的生活中大家都需要有网络的陪伴 相比于手机的4G流量和无线网卡 大家更钟爱于wifi 因为它能够更加方便的使用 可是在我们刚刚购买或者安装路由器的时候也会遇到很多的难题 比如说什么是arp arp映射表有什么作用 接下来就让我们
  • 关于医学影像中的轴位面(横断面)、冠状面、矢状面

    冠状位矢状位轴位图解 第1页 概述 该页主题为冠状位矢状位轴位图解的图片集 内容包含有冠状位 矢状位 横断位具体怎么辨别 谢谢 ct 解剖 颞骨大体解剖 轴位及冠状位ct断层图像 解剖 颞骨大体解剖 轴位及冠状位ct断层图像 图1 a 矢状
  • vue制作幻灯片时涉及的transition动画(动图)

    幻灯片使用频率很高 就是各个网站的轮播大图 为了使图片更加平滑的过渡 就考虑给幻灯片加上transition动画 先看实现的效果 然后再分析动画原理 上图可以看出 幻灯片是慢慢的滑出来 而不是一下一下的跳出来 1 transition动画原
  • 前端面试--大众点评

    学习了这么久 第一次面试前端 虽然只是电话面试 但是还是很紧张 主要问题 1 介绍你的项目 2 html的状态 3html5新增加的标签 4 css的display none和visibility区别 5 怎么清除浮动 6 jquery的选
  • 【TensorFlow 入门】7、定义图变量的方法

    1 tf Variable tf Variable init initial value trainable True collections None validate shape True name None 参数名称 参数类型 参数含
  • DNS服务器列表

    Public DNS IPv4 地址 首选 119 29 29 29 AliDNS 阿里公共 DNS IPv4 地址 首选 223 5 5 5 备用 223 6 6 6 114 DNS 常规公共 DNS 干净无劫持 首选 114 114 1
  • SpringCloud---Sentinel

    文章目录 限流 sentinel使用环境搭建 设置限流 默认直接模式 关联模式 链路模式 关闭URL PATH聚合 熔断 降级 设置模拟环境 满调用比例规则 Sentinel 异常处理模式 异常处理 自定义异常处理 热点 测试热点环境搭建
  • 用mysqldump备份及结合binlog日志恢复的全过程

    1 查看更新备份时的数据 mysql gt select from t1 id 1 2 3 2 因为我的存储引擎是Myisam 为了保证数据的一直我加了参数 l 备份时不能对数据更新 如果是innodb引擎加参数 single transc
  • Speed up calculation by running in parallel

    原文链接 https perlmaven com speed up calculation by running in parallel In this example we have a bunch of numbers We need