我编写了一个编译显示的Perl脚本,以便用户可以查看它们 . 有成千上万的这些显示文件(DSET文件)需要编译,这个过程需要很长时间(4-5小时) . 使用外部可执行文件编译显示(我没有关于此可执行文件的内部工作的详细信息) .
作为加速流程的解决方案,我们决定并行运行此可执行文件的多个实例,以大幅提高性能 .
在使用16个线程运行时,性能显着提高,现在需要大约1小时才能完成,而不是4-5,但仍然存在问题 . 随着脚本的进行,此可执行文件运行的时间也在增加 .
我对大约1000个DSET文件进行了测试,并在Perl脚本进行过程中监视外部编译程序的执行时间 . 下面是执行时间随时间增加的图表 .
如您所见,当脚本启动时,Perl脚本需要大约4秒才能打开可执行文件,编译DSET然后关闭可执行文件 . 一旦脚本处理了大约500个DSET,编译每个后续DSET所花费的时间就会增加 . 当脚本接近结束时,一些DSET文件需要12秒才能编译!
以下是每个线程执行的函数示例:
# Build the displays
sub fgbuilder {
my ($tmp_ddldir, $outdir, $eset_files, $image_files) = @_;
# Get environment variables
my $executable = $ENV{fgbuilder_executable};
my $version = $ENV{fgbuilder_version };
# Create the necessary directories
my $tmp_imagedir = "$tmp_ddldir\\images";
my $tmp_outdir = "$tmp_ddldir\\compiled";
make_path($tmp_ddldir, $tmp_imagedir, $tmp_outdir);
# Copy the necessary files
map { copy($_, $tmp_ddldir ) } @{$eset_files };
map { copy($_, $tmp_imagedir) } @{$image_files};
# Take the next DSET off of the queue
while (my $dset_file = $QUEUE->dequeue()) {
# Copy the DSET to the thread's ddldir
copy($dset_file, $tmp_ddldir);
# Get the DSET name
my $dset = basename($dset_file);
my $tmp_dset_file = "$tmp_ddldir\\$dset";
# Build the displays in the DSET
my $start = time;
system $executable,
'-compile' ,
'-dset' , $dset ,
'-ddldir' , $tmp_ddldir ,
'-imagedir', $tmp_imagedir,
'-outdir' , $tmp_outdir ,
'-version' , $version ;
my $end = time;
my $elapsed = $end - $start;
$SEMAPHORE->down();
open my $fh, '>>', "$ENV{fgbuilder_errordir}\\test.csv";
print {$fh} "$PROGRESS,$elapsed\n";
close $fh;
$SEMAPHORE->up();
# Remove the temporary DSET file
unlink $tmp_dset_file;
# Move all output files to the outdir
recursive_move($tmp_outdir, $outdir);
# Update the progress
{ lock $PROGRESS; $PROGRESS++; }
my $percent = $PROGRESS/$QUEUE_SIZE*100;
{ local $| = 1; printf "\rBuilding displays ... %.2f%%", $percent; }
}
return;
}
每次通过循环它产生一个显示构建可执行文件的新实例,等待它完成然后关闭该实例(这应该释放它正在使用的任何内存并解决任何问题,如我所看到的) .
这些线程中有16个并行运行,每个线程从队列中取出一个新的DSET,编译它并将编译后的显示移动到输出目录 . 一旦显示器被编译,它就会继续从队列中取出另一个DSET并重新启动该过程直到队列耗尽 .
几天来我一直在试图弄清楚它为什么会变慢 . 在此过程中,我的RAM使用率是稳定的,并没有增加,我的CPU使用率没有接近最大值 . 任何有关可能发生的事情的帮助或见解表示赞赏 .
EDIT
我编写了一个测试脚本,试图测试问题是由磁盘I / O缓存问题引起的 . 在这个脚本中,我使用了旧脚本的相同基本体,并用自己的任务将调用替换为可执行文件 .
这是我用以下内容替换可执行文件:
# Convert the file to hex (multiple times so it takes longer! :D)
my @hex_lines = ();
open my $ascii_fh, '<', $tmp_dset_file;
while (my $line = <$ascii_fh>) {
my $hex_line = unpack 'H*', $line;
$hex_line = unpack 'H*', $hex_line;
$hex_line = unpack 'H*', $hex_line;
$hex_line = unpack 'H*', $hex_line;
$hex_line = unpack 'H*', $hex_line;
$hex_line = unpack 'H*', $hex_line;
$hex_line = unpack 'H*', $hex_line;
$hex_line = unpack 'H*', $hex_line;
push @hex_lines, $hex_line;
}
close $ascii_fh;
# Print to output files
make_path($tmp_outdir);
open my $hex_fh, '>', "$tmp_outdir\\$dset" or die "Failed to open file: $!";
print {$hex_fh} @hex_lines;
close $hex_fh;
open $hex_fh, '>', "$tmp_outdir\\2$dset" or die "Failed to open file: $!";
print {$hex_fh} @hex_lines;
close $hex_fh;
open $hex_fh, '>', "$tmp_outdir\\3$dset" or die "Failed to open file: $!";
print {$hex_fh} @hex_lines;
close $hex_fh;
open $hex_fh, '>', "$tmp_outdir\\4$dset" or die "Failed to open file: $!";
print {$hex_fh} @hex_lines;
close $hex_fh;
open $hex_fh, '>', "$tmp_outdir\\5$dset" or die "Failed to open file: $!";
print {$hex_fh} @hex_lines;
close $hex_fh;
open $hex_fh, '>', "$tmp_outdir\\6$dset" or die "Failed to open file: $!";
print {$hex_fh} @hex_lines;
close $hex_fh;
我没有调用可执行文件并编译DSET,而是将每个文件作为文本文件打开,进行一些简单的处理并将一些文件写入磁盘(我每次都会向磁盘写入一些文件,因为可执行文件会将多个文件写入磁盘它处理的每个DSET的磁盘) . 然后我监控了处理时间并绘制了我的结果 .
这是我的结果:
我确实认为我的另一个脚本问题的一部分是磁盘I / O问题,但正如你在这里看到的那样,由于我故意创建的磁盘I / O问题,处理时间的增加并不是渐进的 . 它有一个突然的跳跃,然后结果变得相当不可预测 .
在我之前的脚本中,您可以看到一些不可预测性,并且它正在编写大量文件,因此我毫不怀疑该问题至少部分是由磁盘I / O问题引起的,但这仍然无法解释为什么处理时间的增加是渐进的,并且看起来是恒定的 .
我相信还有一些其他因素在起作用我们没有考虑到 .
1 回答
我认为你只是有磁盘碎片问题 . 鉴于您有多个线程不断创建和删除不同大小的新文件,最终磁盘空间变得非常分散 . 我不知道你运行的是哪个操作系统,我猜它是Windows .
您无法使用测试工具重现这一点的原因可能是因为外部编译器工具的行为 - 它可能会创建输出文件,然后在写入之间的不同时间内多次扩展其大小,这往往会创建重叠的文件它们在多个线程中运行时的磁盘空间,特别是如果磁盘使用率相对较高,例如超过70% . 您测试似乎是序列化文件创建,这避免了并发写入碎片 .
可能的解决方案:
碎片整理磁盘驱动器 . 只需将编译的文件复制到另一个分区/磁盘,删除它们并复制回来应该够了 .
在几个不同的独立分区上运行外部编译器以避免碎片 .
确保您的文件系统具有50%或更多可用空间 .
使用不太容易出现文件系统碎片的操作系统,例如Linux操作系统 .