常见的并行程序设计问题的解决方法
1. 线程过多 线程并不是越多越好,对于某个程序,如果线程过多反而会严重地降低程序的性能。这种影响主要在于: 将给定的工作量划分给过多的线程会造成每个线程的工作量过少,因此可能导致线程启动和终止的开销比程序实际工作的时间还要多。 同时,太多并发线程的存在将导致共享有限硬件资源的开销增大。如保存和恢复寄存器状态的开销、保存和恢复线程cache状态的开销、废除虚存的开销、线程聚集在一起等待获取某个锁。 怎样解决这个问题: 限制可运行线程的个数 将计算线程与I/O线程分离开 使用已有的技术,如OpenMP,线程池等 2. 数据竞争、死锁 死锁,想必大家都很熟悉。这里谈谈一些简单的避免死锁的规则: 加锁的顺序是关键,使用嵌套的锁时必须保证以相同的顺序获取锁 防止发生饥饿:即这个代码的执行是否一定会结束。 不要重复请求同一个锁 越复杂的加锁方案越有可能造成死锁—设计应力求简单 3. 竞争激烈的锁 即使是正确使用锁来避免数据竞争,但如果各线程对锁的竞争很激烈,那么也会引发性能问题。即很多个线程来竞争同一个锁。在这个情况下,可以考虑将资源划分成若干个部分,然后用彼此独立的锁分别保护各个部分。即合理地安排加锁粒度的问题。在早期的Linux内核中就使用了大内核锁,现在已经把锁给细划到不同的子系统中去了。 如果一个数据结构被频繁读取但不被频繁写入,就可以使用读写锁来解决竞争。 4. 线程安全函数 已有的一些函数并不是线程安全的,具体的线程安全函数可以参考APUE2上面的线程一章。 5. 存储效率问题 目前,处理器的速度已经比存储器快很多,一个处理器从内存中读或写一个值的时间内可以完成上百次的操作。因此,现在的程序往往受限于存储器的瓶颈。 首先,可以通过节省带宽,可以减少与存储器的数据交换数量,要节省带宽就要将数据压缩得更加紧凑。 其次是cache的利用。可以减少数据在不同CPU的cache间的移动。CPU亲合力(CPU Affinity)就是指在Linux系统中能够将一个或多个进程绑定到一个或多个处理器上运行。一个进程的CPU亲合力掩码决定了该进程将在哪个或哪几个CPU上运行,在一个多处理器系统中,设置CPU亲合力的掩码可能会获得更好的性能。 在下面的例子中讲解了如何利用将某个进程绑定以某个CPU上运行的实例。 http://linux.chinaunix.net/bbs/v ... p%3Bfilter%3Ddigest |
Cache缓存与一些程序性能优化的小方法:
由于CPU运算速度的增长远远大于内存速度的增长,同时由于程序运行的局部性原理,所以现代计算机体系结构中引入了cache。 其实系统的存储器本身就是一个层次结构,从寄存器->高速缓存->主存->本地磁盘->颁布式文件系统。 在下面的贴子中的一个文档对cache方面讲解得非常不错,可以看看: http://bbs.chinaunix.net/viewthr ... mp;highlight=scutan 高速缓存的性能主要有以下几个方面: 1. 高速缓存的大小: 当CPU接收到指令后,它会最先向CPU中的一级缓存去寻找相关的数据,如果未命中,则继续向下一级的二级缓存中寻找。所以高速缓存越大,命中的机率也就越高。 2. 块大小的影响 大的块有利有弊,一方面,较大的块能利用程序中可能存在的空间局部性,帮助提高命中率。不过,对于给定的高速缓存的大小,块越大就意味着缓存行数越小,这会损害时间局部性比空间局部性更好的程序中的命中率。同时,因为块越大,传达时间也就越长。 3. 相联度的影响 较高的相联度的优点的降低了高速缓存由于冲突不命中出现抖动的可能性。不过,较高的相联度会造成较高的成本。 4. 前端总线的影响 现在的CPU技术发展很快,运算速度提高很大,而足够大的前端总线可以保障有足够的数据供给CPU,较低的前端总线将无法供给足够的数据给CPU,限制了CPU性能的发挥。 最后谈谈程序性能优化的相关内容。 对于程序性能的优化,首先应该考虑的架构以及算法这两方面的优化,最后再来考虑下面所讲述的这些优化。 消除连续的函数调用 消除不必要的存储器引用 通过展开循环降低循环开销 提高并行性 重新排列循环以提高空间局部性 让最常运行部分运行得更快 参考资料: [1]. John L. Hennessy, David A. Patterson. 计算机体系结构:量化研究方法. 第4版 [2]. Shameem Akhter, Jason Roberts. 多核程序设计技术—通过软件多线程提升性能 [3]. Randal E. Bryant, David O’Hallaron. 深入理解计算机系统 [4]. Daniel P.Bovet, Marco Cesati. 深入理解Linux内核 第3版 |