如何在Linux中控制并行任务以避免过多的上下文切换



现在我正在使用Linux执行以下任务:

while read parameter
do
    ./program_a $parameter $parameter.log 2>&1 &
done < parameter_file

每个参数都指要处理的文件的名称。每个文件包含不同数量的要处理的行。

例如:
参数文件包含:

File_A
File_B
File_C

File_A包含1k行,File_B包含10k行,File_C包含1000k行,这意味着在上面的脚本中,program_A分别同时处理1000行、10k行和1000k行。每个任务的处理时间几乎线性地取决于行数,并且每个任务是独立的。

我有6核CPU和12个线程。因为处理时间可能会有所不同,所以在运行File_A和File_B的任务后,只有一个核心将处理File_C的任务。这是在浪费资源。

我想将每个文件拆分为1k行,并同时运行它们。但是对于这个例子,将有1011个任务在运行(每个任务1k)。我认为这将导致一个严重的过度上下文转换问题。也许我可以调整每行中的数字来解决这个问题,但我认为这不是一个好的解决方案。

我的想法是将运行的任务限制为6个任务,这意味着始终使用最大数量的内核来运行,并将上下文切换减少到尽可能少。但我不知道如何修改我的脚本来实现这个目标。有人能给我一些建议吗?

我不会试图通过拆分文件来重新发明负载平衡轮。使用gnu-parallel来处理不同规模的任务的管理。它有很多在一台或多台机器上并行执行的选项。如果你把它设置为,比如说,允许4个进程并行,它就会做到这一点,在一个较短的任务完成时开始一个新任务。

https://www.gnu.org/software/parallel/

https://www.gnu.org/software/parallel/parallel_tutorial.html

这里有一个简单的例子,使用cat作为的替身/程序:

...write a couple of files
% cat > a
a
b
c
% cat > b
a  
b
c
d
% cat > files
a
b
... run the tasks
% parallel cat {1} > {1}.log < files
% more b.log
a
b
c
d

由于您可以拆分文件,我假设您也可以合并文件。在这种情况下,您可以考虑以下快速预处理步骤:

#! /bin/bash
# set the number of parallel threads
CPU=6
rm -f complete.out
# combine all files into one
while read parameter
do
    cat $parameter >> complete.out
done < parameter_file
# count the number of lines
lines=$(wc -l complete.out|cut -d " " -f 1)
lines_per_file=$(( $lines / $CPU + 1 ))
# split the big file into equal pieces named xa*
rm -f xa*
split --lines $lines_per_file complete.out 
# create a parameter file to mimic the old calling behaviour
rm -f new_parameter_file
for splinter in xa* ; do
    echo $splinter >> new_parameter_file
done
# this is the old call with just 'parameter_file' replaced by 'new_parameter_file'
while read parameter
do
    ./program_a $parameter $parameter.log 2>&1 &
done < new_parameter_file

注:

  • 生成的文件的文件名模式xa*在您的设置中可能有所不同
  • 确保每个文件的最后一行实际上都有一个CR/LF

我也认为我可以用等待来实现目标。

事实上,使用wait可以实现目标,即使bashwait不幸地等待指定集合的每个进程,而不是等待任何一个进程(也就是说,我们不能简单地指示bash等待所有运行的最早完成进程),但由于

每个任务的处理时间几乎线性地依赖于行数

我想将每个文件拆分为1k行

我们可以很好地近似地说,首先开始的过程也首先结束。

我假设您已经实现了将文件拆分为1000行(如果需要,我可以添加详细信息),并且它们的名称存储在变量$files中,在您的示例File_A000 File_B000 … File_B009 File_C000 … File_C999中。

set --                                  # tasks stored in $1..$6
for file in $files
do  [ $# -lt 6 ] || { wait $1; shift; } # wait for and remove oldest task if 6
    ./program_a $file $file.log 2>&1 &
    set -- $* $!                        # store new task last
done
wait                                    # wait for the final tasks to finish

我假设program_a可以读取单个文件。

然后这应该使用GNU并行:

parallel --pipepart --block 10k --cat program_a :::: File_A File_B File_C

10k调整为1000行的大小。

它的作用与@Marcus Rickert的回答大致相同,但对你隐藏了复杂性,并清理了临时文件。

如果program_a可以从fifo读取,这应该更快:

parallel --pipepart --block 10k --fifo program_a :::: File_A File_B File_C

如果program_a可以从stdin读取,它将更短:

parallel --pipepart --block 10k program_a :::: File_A File_B File_C

如果真的必须有1000个参数,请尝试:

cat File_A File_B File_C | parallel --pipe -L1000 -N1 --cat program_a

或:

cat File_A File_B File_C | parallel --pipe -L1000 -N1 program_a

最新更新