线程:在这种情况下值得吗?

时间:2018-08-29 19:45:33

标签: multithreading perl optimization refactoring

我以前从未使用过线程,但认为我可能遇到过机会:

我编写了一个脚本,该脚本仔细查看了约500个Excel文件的数组,并使用Parse :: Excel从工作簿中的特定工作表中提取值(平均每个工作簿中有两张工作表;每张工作表中提取了一个单元格。)

现在运行它,我只逐个浏览文件阵列并从文件中提取相关信息,大约需要45分钟才能完成。

我的问题是:这是否是使用线程的机会,一次有多个文件被击中*,还是应该接受45分钟的运行时间?

(*-如果这是对我可以使用线程做什么的严重误解,请这么说!)

在此先感谢您提供的任何指导!

编辑-添加示例代码。下面的代码是一个子,在数组中为存储在数组中的每个文件位置在foreach循环中调用:

# Init the parser
my $parser = Spreadsheet::ParseExcel->new;
my $workbook = $parser->parse($inputFile) or die("Unable to load $inputFile: $!");

# Get a list of any sheets that have 'QA' in the sheet name
foreach my $sheet ($workbook->worksheets) {
    if ($sheet->get_name =~ m/QA/) {
        push @sheetsToScan, $sheet->get_name;
    }
}
shift @sheetsToScan;

# Extract the value from the appropriate cell
foreach (@sheetsToScan) {
    my $worksheet = $workbook->worksheet($_);
    if ($_ =~ m/Production/ or $_ =~ m/Prod/) {
        $cell = $worksheet->get_cell(1, 1);
        $value = $cell ? $cell->value: undef;
        if (not defined $value) {
            $value = "Not found.";
        }
    } else {
        $cell = $worksheet->get_cell(6,1);
        $value = $cell ? $cell->value: undef;
        if (not defined $value) {
            $value = "Not found.";
        }
    }

push(@outputBuffer, $line);

3 个答案:

答案 0 :(得分:3)

线程(或使用fork使用多个进程)允许您的脚本一次使用多个CPU。对于许多任务,这可以节省很多“用户时间”,但不会节省“系统时间”(甚至可能会增加系统时间来处理启动和管理线程及进程的开销)。在以下情况下,线程化/多处理将有用:

  • 脚本的任务不适合并行化-算法的每个步骤都取决于前面的步骤

  • 与创建和管理新线程或新进程的开销相比,脚本执行的任务既快速又轻巧

  • 您的系统只有一个CPU,或者您的脚本仅被启用为使用一个CPU

  • 您的任务受与CPU不同的资源约束,例如磁盘访问,网络带宽或内存-如果您的任务涉及处理通过慢速网络连接下载的大文件,则您的网络就是瓶颈,并且无法在多个CPU上处理文件。同样,如果您的任务占用了系统内存的70%,那么使用第二个和第三个线程将需要分页到交换空间,并且不会节省任何时间。如果您的线程争用某些同步资源(文件锁,数据库访问等),则并行化的效果也会降低。

  • 您需要考虑系统上的其他用户-如果您正在使用计算机上的所有内核,那么其他用户的体验将会很差

  • [仅添加线程],您的代码使用了不是线程安全的任何程序包。大多数纯Perl代码都是线程安全的,但是使用XS的软件包可能不是

  • [添加],当您仍在积极开发核心任务时。在并行代码中调试要困难得多

即使这些都不适用,有时也很难说出一项任务将从并行化中受益多少,并且唯一可以确定的是实际实现并行任务并对其进行基准测试。但是您所描述的任务看起来很适合并行化。

答案 1 :(得分:2)

在我看来,您的任务应该受益于多个执行线程(进程或线程),因为它似乎具有I / O和CPU的大致混合。我希望速度提高几倍,但是在不知道细节的情况下很难说出来。我建议尝试。

一种方法是将文件列表分成几组,尽可能多地保留一些核心。然后在fork中处理每个组,然后将其组合起来,并通过管道或文件将它们传递回父级。有些模块可以执行此操作,例如Forks::SuperParallel::ForkManager。他们还提供队列,这是您可以使用的另一种方法。

当涉及到文件中的大量数据时,我会定期执行此操作,并根据多达4个或5个内核(在NFS上),或者甚至根据具体的工作细节和硬件而使用更多的内核,实现接近线性的加速。

我会谨慎地断言,这可能比线程更简单,因此请首先尝试。

另一种方法是创建线程队列(Thread::Queue) 并提供文件名组。请注意, Perl的线程不是人们所期望的轻量级“线程” 。相反,它们很繁重,它们将所有内容复制到每个线程(因此,在程序中没有大量数据之前,先启动它们),并且它们还带有其他一些细微之处。因此,只有很少的工作人员为每个工作人员提供一个不错的文件列表,而不是让许多线程快速使用队列。

在这种方法中,也要小心如何将结果传递回去,因为根据我的经验,频繁的通信会对(Perl的)线程造成相当大的开销。

在任何一种情况下,组的形成都是重要的,以便为每个线程/进程提供平衡的工作负载。如果这是不可能的(您可能不知道哪些文件可能花费比其他文件更长的时间),则线程应该占用较小的批处理,而对于派生使用模块中的队列。

仅将一个文件或几个文件交给线程或进程,很可能会减轻工作量,在这种情况下,管理开销可能会消除(或逆转)可能的速度提升。线程/进程之间的I / O重叠也会增加,这是此处加速的主要限制。

即使掌握了所有详细信息,也很难估计传递给线程/进程的最佳文件数量;尝试。我认为报告的性能(文件超过5秒)是由于效率低下可以消除的。如果某个文件确实确实需要花费那个长时间来处理,则从一次将单个文件传递到队列开始。

另外,请仔细考虑mob's answer。请注意,这些都是高级技术。


实用评论:正如Shawn的评论中所述,花费45分钟的时间从500个文件中提取一些数据似乎非常极端(除非所使用的模块非常无效)。 ;我最多要给它 几分钟? 因此,请先检查您的代码,以确保效率低下。

答案 2 :(得分:0)

您要做的只是将“ for ....”更改为“ mce_loop ....”,尽管您建议您先看看mceloop,但您会看到很大的帮助。