深入理解并行编程-分割和同步设计(三)
原文链接 作者:paul 译者:谢宝友,鲁阳,陈渝
设计准则
上面的章节中给出了三个并行编程的目标:性能、生产率和通用性。但是还需要更详细的设计准则来真正的指导真实世界中的设计,这就是本节将解决的任务。在真实世界中,这些准则经常在某种程度上冲突,这需要设计者小心的权衡得失。
这些准则可以被认为是设计中的阻力,对这些阻力进行恰当权衡,这就称为“设计模式”[Ale79],[GHJV95]。
基于三个并行编程目标的设计准则是加速、竞争、开销、读写比率和复杂性。
加速:性能增加是花费如此多时间和精力进行并行化的主要原因。加速的定义是运行程序的顺序执行版本所需要的时间,比上执行并行版本所需时间的比例。
竞争:如果对于一个并行程序来说,增加更多的CPU并不能让程序忙起来,那么多出来的CPU是因为竞争的关系而无法有效工作。可能是锁竞争、内存竞争或者其他什么性能杀手的原因。
工作-同步比率:单处理器、单线程、不可抢占、不可中断版本的并行程序完全不需要任何同步原语。因此,任何消耗在这些原语上(通信中的cache miss、消息延迟、加解锁原语、原子指令和内存屏障等)的时间都是对程序意图完成的工作没有直接帮助的开销。重要的衡量准则是同步开销与临界区中代码的开销之间的关系,更大的临界区能容忍更大的同步开销。工作-同步开销比率与同步效率的概念有关。
读-写比率:极少更新的数据结构更多是复制而不是分割,并且用非对称的同步原语来保护,以提高写者同步开销的代价来降低读者的同步开销。对频繁更新的数据结构的优化也是可以的,详见第4章的讨论。
复杂性:并行程序比相同的顺序执行程序复杂,这是因为并行程序要比顺序执行程序维护更多状态,虽然这些状态在某些情况下(有规律并且结构化的)理解起来很容易。并行程序员必须考虑同步原语、消息传递、锁的设计、临界区识别以及死锁等诸多问题。
更大的复杂性通常转换成了更高的开发和维护代价。因此,代码预算可以有效限制对现有程序修改的数目和类型,因为原有程序在一定程度的加速只值这么多的时间和精力。进一步说,还有可能对顺序执行程序进行一定程度的优化,这比并行化更廉价、更有效。并行化只是众多优化中的一种,并且只是一种主要应用于CPU是瓶颈的优化。
这些准则合在一起,让程序达到了最大程度的加速。前三个准则相互交织在一起,所以本节剩下的部分将分析这三个准则的交互关系。
请注意,这些准则也是需求说明的一部份。必须,加速既是愿望(“越快越好”),又是工作负荷的绝对需求,或者说是“运行环境”(“系统必须至少支持每秒1百万次的web点击”)。
理解这些设计准则之间的关系,对于权衡并行程序的各个设计目标十分有用。
1. 程序在临界区上所花的时间越少,潜在的加速就越大。这是Amdahl定律的结果,这也是因为在一个时刻只能只有一个CPU进入临界区的原因。
2. 程序在某个互斥的临界区上所耗费的时间必须大大小于CPU数的倒数,因为这样增加CPU数目才能达到事实上的加速。比如在10个CPU上运行的程序只能在关键的临界区上花费小于1/10的时间,这样扩展性才好。
3. 竞争效应所浪费的多余CPU和/或者本来该是加速的墙上时间,应该少于可用CPU的数目。CPU数和实际的加速之间的差距越大,CPU使用的效率越低。同样,需要的效率越高,可以做到的加速就越小。
4. 如果可用的同步原语相较它们保护的临界区来说开销太大,那么加速程序运行的最佳办法是减少调用这些原语的次数(比如分批进入临界区、数据所有权、RCU或者使用粒度更粗的设计——比如代码锁)。
5. 如果临界区相较守护这块临界区的原语来说开销太大,那么加速程序运行的最佳办法是增加程序的并行化程度,比如使用读写锁、数据锁、RCU或者数据所有权。
6. 如果临界区相较守护这块临界区的原语来说开销太大,并且被守护的数据结构读多于写,那么加速程序运行的最佳办法是增加程序的并行化程度,比如读写锁或者RCU。
7. 各种增加SMP性能的改动,比如减少锁竞争程度,能改善响应时间。
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 深入理解并行编程-分割和同步设计(三)
暂无评论