【问题标题】:Parallel-ForkManager, DBI. Faster than before forking, but still too slowParallel-ForkManager,DBI。比叉子前更快,但仍然太慢了
【发布时间】:2015-05-08 11:02:22
【问题描述】:

我有一个更新数据库的非常简单的任务。

my $pm = new Parallel::ForkManager(15);
for my $line (@lines){
    my $pid = $pm->start and next;
    my $dbh2 = $dbh->clone();
    my $sth2 = $dbh2->prepare("update db1 set field1=? where field2 =?");           
    my ($field1, $field2) = very_slow_subroutine();
    $sth2->execute($field1,$field2);
    $pm->finish;        
} 
 $pm->wait_all_children;    

我可以只使用 $dbh2->do,但我怀疑它是缓慢的原因。

有趣的是,这 15 个进程(或我指定的任何进程)的启动速度似乎非常快,但之后速度急剧下降,仍然比没有分叉的速度更快,但我期待更多......

编辑:

very_slow_subroutine 是从 Web 服务获取答案的子程序。该服务可以响应从几分之一秒到几秒的超时时间。我要问几十万次……我想做叉子的原因。

如果这很重要——我在 Linux 上。

【问题讨论】:

  • 您可能需要发布/重构 very_slow_subroutine 以获得更快的速度。

标签: performance perl dbi fork


【解决方案1】:

Parallel::ForkManager 不会神奇地让事情变得更快,它只是让您可以同时多次运行代码。为了从中受益,您必须针对并行性设计代码。

这样想。您需要 10 分钟才能到达商店、购物、装车、返回和卸车。您需要获得 5 个负载。你一个人可以在 50 分钟内完成。那是串行工作。 10 分钟 * 5 次接连 = 50 分钟。

假设您有四个朋友来帮忙。你们都同时出发去商店。还有 5 次行程,它们仍然需要 10 分钟,但因为您并行执行,所以总时间只有 10 分钟。

但无论您要进行多少次旅行或获得多少朋友的帮助,时间都不会少于 10 分钟。这就是为什么这个过程开始得很快,每个人都上车开车去商店,但有一段时间什么都没有发生,因为每个人都需要 10 分钟才能完成他们的工作。

这里也一样。你的循环体需要 X 时间来运行。如果你迭代它 Y 次,它将花费 X * Y 真实世界的人类时间来运行。如果你并行运行它 Y 次,理想情况下它只需要 X 时间运行。每个并行工作者仍然必须执行整个循环体,花费 X 时间。

为了进一步加快速度,您必须打破very_slow_subroutine 的大瓶颈并使 并行工作。您的 SQL 非常简单,因此您应该将精力集中在优化和并行性上。

假设商店真的很近,开车只需 1 分钟(这是您的 SQL UPDATE),但购物、加载和卸载需要 9 分钟(这是very_slow_subroutine)。如果你有 5 辆汽车和 15 个朋友呢?每辆车上载 3 个人。开车往返商店需要同样的时间,但现在三个人一起购物,装卸只需4分钟。现在每次旅行需要 5 分钟而不是 10 分钟。

这代表重新设计very_slow_subroutine 以并行完成其工作。如果它只是一个大循环,您可以在该循环上放置更多工作人员。如果是一系列缓慢的操作,您将不得不重新设计它以利用并行执行。

如果您使用过多的工人,您可能会堵塞系统,这取决于瓶颈是什么。如果它受 CPU 限制并且您有 2 个 CPU 内核,您可能会看到多达 3 到 5 个工作人员的性能提升((cores * 2)+1 是一个很好的经验法则),然后随着 CPU 花费更多时间在过程胜于工作。如果瓶颈是 IO,或者是数据库和网络调用中经常出现的外部服务,那么您可以看到很多工作人员解决问题的高效率。当一个进程在等待磁盘或网络操作时,其他进程可能正在使用您的 CPU。

【讨论】:

  • 感谢您提供这样的图像解释 :) 是的,我必须早点告诉,非常慢的子程序是 Web 服务。它可以在几分之一秒内回答到 30 秒后超时。我只能控制超时,所以它不会等待所有 30。我现在将编辑我的问题。
  • @MikeKlemin 在这种情况下,您正在做的就是您将从并行性中获得的最好的结果。投入更多的工人将有助于整体表现。如果您怀疑您多次进行相同的查询,您可能会做的是查看它们是否具有缓存机制,例如 Github Conditional Requests 或自己编写缓存。您还需要关闭DBI AutoCommit,这样您就不会浪费时间在每次迭代中提交事务。
  • 谢谢。我不确定是否可以不使用 AutoCommit 的问题,因为每个连接都在分叉内启动和结束。我虽然使用可共享列表首先将数据收集到列表中,然后运行一次大更新,但经过一些测试,似乎没有太大的速度提升,正如你所说的更新不是瓶颈......
【解决方案2】:

并行性是否有帮助取决于您的瓶颈在哪里。如果您的 4 核 CPU 是瓶颈,那么分叉 4 个进程可能会导致事情在最佳情况下的大约 1/4 内完成,但生成 15 个进程并不会进一步改善事情。

如果更可能的是,您的瓶颈在于 I/O,则启动 15 个竞争相同 I/O 的进程不会有太大帮助,尽管在您有大量内存用作文件缓存的情况下,@ 987654321@可能是可能的。

要探索系统的限制,请考虑以下程序:

#!/usr/bin/env perl

use strict;
use warnings;

use Parallel::ForkManager;

run(@ARGV);

sub run {
    my $count = @_ ? $_[0] : 2;
    my $pm = Parallel::ForkManager->new($count);
    for (1 .. 20) {
        $pm->start and next;
        sleep 1;
        $pm->finish;
    }
    $pm->wait_all_children;
}

我的老式笔记本电脑有一个带 2 个内核的 CPU。让我们看看我得到了什么:

TimeThis:命令行:perl sleeper.pl 1
TimeThis:经过的时间:00:00:20.735

TimeThis:命令行:perl sleeper.pl 2
TimeThis:经过的时间:00:00:06.578

TimeThis:命令行:perl sleeper.pl 4
TimeThis:经过的时间:00:00:04.578

TimeThis:命令行:perl sleeper.pl 8
TimeThis:经过的时间:00:00:03.546

TimeThis:命令行:perl sleeper.pl 16
TimeThis:经过的时间:00:00:02.562

TimeThis:命令行:perl sleeper.pl 20
TimeThis : 经过的时间 : 00:00:02.563

因此,最多运行 20 个进程使我的总运行时间超过 2.5 秒,一秒睡眠 20 次。

另一方面,只有一个进程,一秒钟休眠 20 次只需要 20 多秒。这是一个巨大的改进,但它也表明当您有 20 个进程每个都休眠一秒钟时,管理开销超过 150%。

这是并行编程的本质。有很多关于你可以期待的正式治疗,但Amdahl's Law 是必读的。

【讨论】:

    猜你喜欢
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    • 2012-01-19
    • 2015-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多