我在C#中完成了一项非常简单的任务。用户给我一个表示大型二进制文件(大小为几十GB)的流。该文件由许多不同的块组成。我需要阅读每个块;对每个块执行一些CPU密集型分析;然后以正确的顺序给用户结果。在伪代码中,此代码可能如下所示:
public IEnumerable<TResult> ReadFile(Stream inputStream) {
while(true) {
byte[] block = ReadNextBlock(stream);
if (block == null) {
break; // EOF
}
TResult result = PerformCpuIntensiveAnalysis(block);
yield return result;
}
}
这可以正常工作,但速度很慢,因为它只使用一个CPU内核进行CPU密集型分析。我想要做的是逐个读取块,并行分析它们,然后以与文件中遇到的块相同的顺序将结果返回给用户。当然,我无法将整个文件读入内存,所以我想在任何给定时间限制我保留在队列中的块数。
有很多解决方案,我已经尝试了一对;但是,出于某种原因,我找不到任何明显优于天真方法的解决方案:
public IEnumerable<TResult> ReadFile(Stream inputStream) {
while(true) {
var batch = new List<byte[]>();
for (int i=0; i<BATCH_SIZE; i++) {
byte[] block = ReadNextBlock(stream);
if (block == null) {
break;
}
batch.Add(block);
}
if (batch.Count == 0) {
break;
}
foreach(var result in batch
.AsParallel()
.AsOrdered()
.Select(block => PerformCpuIntensiveAnalysis(block))
.ToList()) {
yield return result;
}
}
}
我尝试过TPL /数据流以及纯粹的手动方法,在每种情况下,我的代码都花费大部分时间等待同步。它的性能优于串行版本约2倍,但在具有8个内核的机器上,我预计会有更多。那么,我做错了什么?
(我还应该澄清一点,我并没有真正使用&#34; yield return&#34;我的代码中的生成器模式,我只是为了简洁而在这里使用它。)
答案 0 :(得分:1)
尝试优化块大小。
如果块太少而且其中一块比其他块需要更长的时间,那么只有一个CPU必须完成几乎所有的工作。
另一方面,如果块太小,TPL将花费大量时间来处理与任务管理相关的开销。
你应该拥有比CPU更多的块。这允许TPL将工作均匀地分配给CPU。另一方面,一个块应该需要大量的计算工作。所以很难给出具体的数字。