如何优化用于多项目C#文件查找的简易TPL DataFlow流水线性能?
TPL DataFlow流水线性能优化方案
现有实现的性能瓶颈分析
你的基础流水线逻辑是正确的,但默认配置和几个细节没做优化,导致并行收益没完全释放:
ExecutionDataflowBlockOptions默认开启EnsureOrdered:为了保证输出顺序和输入顺序一致,内部会产生大量同步开销,你的场景不需要顺序保证,关闭后可直接降低调度成本- 未设置
BoundedCapacity:默认块的输入队列无上限,会导致上游块一次性生成大量CSFile对象积压在内存,GC压力陡增,同时流水线背压机制完全失效,各阶段负载不均衡 - 末尾
ActionBlock的Console.WriteLine存在锁竞争:Console类的输出方法是全局线程安全的,内部有锁,匹配结果多的时候锁竞争会严重拖慢整个流水线的吞吐量 - 混合负载的DOP设置不合理:项目求值阶段是IO+CPU混合负载,MSBuild API内部本身也有并行逻辑,外部DOP开太高会出现资源争抢,反而导致性能下降
- 测试使用Debug版本程序:Debug版本关闭了大部分运行时优化,测试结果会比实际Release版本差很多
优化代码示例
private async Task RunOptimized(string workspaceRoot, string literal, int maxDOP1 = 3, int maxDOP2 = 4) { var projects = (workspaceRoot + "build\\projects.yml").YieldAllProjects(); var processorCount = Environment.ProcessorCount; // 阶段1:解析项目生成C#文件列表 var produceCSFiles = new TransformManyBlock<ProjectEx, CSFile>(YieldCSFiles, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = maxDOP1, EnsureOrdered = false, // 关闭顺序保证,减少同步开销 BoundedCapacity = processorCount * 2 // 限制队列长度,启用背压 }); // 阶段2:搜索文件匹配行,建议将YieldMatchingLines改成异步文件读取实现,进一步提升IO效率 var produceMatchingLines = new TransformManyBlock<CSFile, MatchingLine>( csFile => csFile.YieldMatchingLines(literal), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = maxDOP2, EnsureOrdered = false, BoundedCapacity = processorCount * 4 }); // 阶段3:收集结果,避免实时输出的锁竞争 var results = new ConcurrentBag<MatchingLine>(); var collectResults = new ActionBlock<MatchingLine>( o => results.Add(o), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, BoundedCapacity = DataflowBlockOptions.Unbounded }); var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; produceCSFiles.LinkTo(produceMatchingLines, linkOptions); produceMatchingLines.LinkTo(collectResults, linkOptions); Console.WriteLine($"Locating all the instances of {literal} in the C# code ... "); var sw = Stopwatch.StartNew(); foreach (var p in projects) { // 用SendAsync配合背压,避免消息丢失或队列溢出 await produceCSFiles.SendAsync(p); } produceCSFiles.Complete(); await collectResults.Completion; sw.Stop(); // 处理完成后一次性输出所有结果 foreach (var res in results) { Console.WriteLine(res.ToString(workspaceRoot)); } Console.WriteLine($"Elapsed: {sw.Elapsed}"); }
优化后的预期收益
- 关闭
EnsureOrdered可直接获得15%-25%的性能提升 - 增加
BoundedCapacity降低GC压力后可再获得10%左右的提升 - 去掉实时
Console输出的锁竞争,若匹配结果较多可再获得10%以上的提升 - 换成Release版本编译运行,还能额外获得10%-15%的性能提升
整体性能相比你当前的最优版本还能再提升30%以上,相比单线程版本可以获得接近60%的性能提升。
内容的提问来源于stack exchange,提问作者mark
相关产品推荐
相关产品推荐

