有什么办法可以通过TPL限制来限制性能下降吗?
我有一个复杂的组件管道,并试图限制所需的内存要求。我从多个文件并行读取,管道中的组件可能会从这些文件的随机部分读取一些内容,其余组件执行CPU绑定操作。
我使用通用的测试方法将性能测试平台简化为这些测试。
private void TPLPerformaceTest(int generateNumbers, ExecutionDataflowBlockOptions transformBlockOptions)
{
var transformBlock = new TransformBlock<int, int>(i => i, transformBlockOptions);
var storedCount = 0;
var generatedCount = 0;
var store = new ActionBlock<int>(i => Interlocked.Increment(ref storedCount));
transformBlock.LinkTo(store);
transformBlock.Completion.ContinueWith(_ => store.Complete());
for (int i = 0; i < generateNumbers; i++)
{
transformBlock.SendAsync(i).Wait(); //To ensure delivery
Interlocked.Increment(ref generatedCount);
}
transformBlock.Complete();
store.Completion.Wait();
Assert.IsTrue(generatedCount == generateNumbers);
Assert.IsTrue(storedCount == generateNumbers);
}
第一个无限制。在我的CPU上完成需要 12s ,消耗大约 800MB 的RAM,平均CPU利用率大约 35%。
[Test]
public void TPLPerformaceUnlimitedTest()
{
var generateNumbers = 100000000;
this.TPLPerformaceTest(generateNumbers,new ExecutionDataflowBlockOptions());
}
第二次测试只是将 BoundedCapacity设置为int.MaxValue ,因此完全没有限制,需要 20-30s 才能完成,消耗 2.1GB 强大的RAM和平均CPU利用率大约 50%。根据手册,BoundedCapacity默认设置为int.MaxValue,所以我没有看到性能下降的原因。
[Test]
[Sequential]
public void TPLPerformaceBounedCapacityTest()
{
var generateNumbers = 100000000;
this.TPLPerformaceTest(generateNumbers,new ExecutionDataflowBlockOptions()
{ BoundedCapacity = Int32.MaxValue });
}
第三个测试限制 BoundedCapacity to generateNumbers / 1000 ,ergo 100000.完成需要60秒,消耗 450MB 的RAM,平均CPU利用率约为 60 %
[Test]
[Sequential]
public void TPLPerformaceBounedCapacityTenthTest()
{
var generateNumbers = 100000000;
this.TPLPerformaceTest(generateNumbers,new ExecutionDataflowBlockOptions()
{ BoundedCapacity = generateNumbers / 1000 });
}
第四次测试将 MaxDegreeOfParallelism限制为-1 ,这是根据手册无限制。它消耗了 27GB 的RAM,平均CPU利用率大约 85%,并且还没有完成 5分钟。
[Test]
[Sequential]
public void TPLPerformaceMaxDegreeOfParallelismTest()
{
var generateNumbers = 100000000;
this.TPLPerformaceTest(generateNumbers, new ExecutionDataflowBlockOptions()
{ MaxDegreeOfParallelism = -1 });
}
所有方法似乎都很难影响性能,并且由于我的合理期望而无法表现。
答案 0 :(得分:5)
<击> 由于这一行你正在降低性能:
transformBlock.SendAsync(i).Wait(); //To ensure delivery
此阻止当前线程在传递完成之前。您应切换到await
以释放线程以继续执行其他任务:
await transformBlock.SendAsync(i); //To ensure delivery
击> <击> 撞击>
更新
我对你的话感到困惑
根据手册,
BoundedCapacity
默认设置为int.MaxValue
因为事实并非如此,from official documentation:
BoundedCapacity
System.Threading.Tasks.Dataflow.dll
中包含的大多数数据流块都支持有界容量的规范 这是块可能存储并且在任何时间都在飞行中的物品数量的限制 默认情况下,此值初始化为DataflowBlockOptions.Unbounded
(-1
),表示没有限制。
在此处,您可以在运行此代码后看到所有默认值:
var options = new ExecutionDataflowBlockOptions();
所以将BoundedCapacity
设置为int.MaxValue
的第二次测试会添加一个限制,这会在块的缓冲区中添加一些位置可用性检查。
你可以在第三次测试中看到类似的行为,它比第二次测试消耗的内存少得多,但是对缓冲区做更多的检查和等待时间来释放空间,所以它的工作速度较慢,但需要很少的内存来分配。
此外,您可以在屏幕截图中看到MaxDegreeOfParallelism
等于1
:
MaxDegreeOfParallelism
默认情况下,单个数据流块一次只处理一条消息,对所有尚未处理的消息进行排队,以便在当前处理消息完成时对它们进行处理。
将此参数设置为-1
后,打开潘多拉魔盒,因为所有消息都由同一任务调度程序同时执行 ,再次according to the documentation :
如果设置为
DataflowBlockOptions.Unbounded
(-1
),则可以同时处理任意数量的消息,最大值由数据流块所针对的基础调度程序自动管理。
正如我在内存消耗中看到的那样,任务调度程序决定为每个消息启动新线程,因为线程池中没有可用的消息,每个消息大约需要1MB
,所以你有一个大约{{ {1}}线程互相争夺CPU时间。正如你所看到的,他们并不擅长这一点
建议的并行度通常为27000
,因此,如果您想加快一个块的速度,可以将Environment.ProcessorCount
设置为此属性。但是,在更复杂的情况下,这可能并不总是最佳选择,因为其他块将在等待CPU时间时停止。
那么你的MaxDegreeOfParallelism
是什么?