BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

.NET 4.5中任务并行类库的改进

| 作者 Jonathan Allen 关注 530 他的粉丝 ,译者 侯伯薇 关注 0 他的粉丝 发布于 2011年12月9日. 估计阅读时间: 6 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

微软正在努力改进.NET 4.5中应用程序的性能,特别是使用任务并行类库(Task Parallel Library)的那些应用。接下来我会带你预览将要完成的改进内容:

Task, Task<TResult>

.NET并行编程API的核心是Task对象。对于这样重要的类,微软想法设法保证它要尽可能小。Task的大多数属性都没有保存在类本身之中,而是保存在另一个名为ContingentProperties的对象中。这个二级对象会在程序需要的时候才创建,这样就会降低大多数一般情况下的内存占用。

.NET 4.0发布的时候,最常见的情形是分支合并(fork-join)样式的编程,就像我们在Parallel.ForEach和Parallel LINQ中看到的那样。然而,有了.NET 4.5和其中引入的异步机制,顺序样式的编程就取而代之,占据主导地位。微软非常确信这会是主要的方式,因此他们把ContinuationObject移动到Task中,把其他字段移动到ContingentProperties中。这使得顺序结构的代码运行更快,而Task对象的规模更小。

Task<TResult> 也避免了一些不需要的等待。它最初拥有四个属性,但是Joseph E. Hoag解释说

由于我们进行了一些很聪明的结构调整,结果只有m_result字段才是真正必要的。通过对已经存在于基本的Task类中的字段重新利用,我们可以废弃m_valueSelector和m_futureState字段,而存储在m_resultWasSet中的信息可以存储在基本类型的上述状态标识中。

结果创建Task<Int32>所需的时间会减少49-55%,对象的大小会减少52%。

Task.WaitAll, Task.WaitAny

试想一下,我们需要同时等待十亿个任务。在一台x64的计算机上,这会导致12,000,000比特的负载,这还没有计算任务本身。如果使用.NET 4.5,负载会降到仅仅64比特。同时WaitAny的负载也会从23,200,000比特降到152比特。

之所以出现如此戏剧化的效果,是因为微软改变了使用核心同步基元(kernel synchronization primitives)的方式。在之前的版本中,每个任务都需要一个基元(primitive )。现在已经大大减少,每个等待操作只需要一个基元,与操作中的任务数量无关。

ConcurrentDictionary

在.NET中,只有引用类型和很小的值类型才能够以原子的方式赋值。较大的值类型——像Guid——则无法以原子的方式读写。在.NET 4.0中,为了解决这个问题,ConcurrentDictionary会使用node对象,每次与键值关联的值发生改变的时候,都会重新创建这个对象。在.NET 4.5中,只有在无法以原子的方式对值进行写操作的时候,才会创建新的node对象。

另一项改变是我们可以动态地创建锁。Igor Ostrovsky写到

在实践中,为了达到最大吞吐量,往往需要大量锁。另一方面,我们又不希望分配太多锁对象,特别是在ConcurrentDictionary最后只存储了很少项目的时候。

想要提升性能,就要减少内存分配

Joseph写到:

在我们的评测结果中你可以看到,在测试中分配的内存数量和完成测试所需的时间之间有直接关系。当我们单独查看的时候,内存分配并不是非常昂贵。但是,当内存系统只是偶尔清理不使用的内存时,问题就出现了,并且问题出现的频率和要分配的内存数量成正比。因此,你分配越多的内存,对内存进行垃圾回收的频率就越频繁,你的代码性能就会变得越差。

想要降低内存使用,一种方式就是避免使用闭包(closure)。不要在匿名的函数中捕获局部变量,我们可以把它传递给Task的构造函数,作为它的“状态(state)对象”。从.NET 4.5开始,Task.ContinueWith也会支持状态对象。

另一种减少内存使用的技术是缓存经常使用的任务。例如,假设一个函数会接受一个数组作为参数,并返回Task<int>。因为对于空数组结果总会是一样的,所以缓存代表空数组的Task就很合理。

下一个技巧是避免让任务不必要地“膨胀”。当某些代码触发了创建ContingentProperties的操作,Task对象就会膨胀。最经常出现的原因包括:

  • 创建的任务带有CancellationToken
  • 任务是从非默认的ExecutionContext创建的
  • Task作为父Task参与到“结构化并行机制(structured parallelism)”中
  • Task以Faulted状态结束
  • Task通过((IAsyncResult)Task).AsyncWaitHandle.Wait()处于等待状态

大家还要记住,任务膨胀并不一定是坏事。它只是需要注意的问题,这样我们就不会做不需要的事情,像传入从来不会用到的CancellationToken等。

查看英文原文:Task Parallel Library Improvements in .NET 4.5

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

并行库什么的最有技术含量了 by Jeffrey Zhao

所以千万要用现成的不要自己写……

Re: 并行库什么的最有技术含量了 by zheng yangyong

同意。。。前一段时间用TPL在服务器,运行了几天Thread数卡到了1000多无法释放

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

2 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT