免费午餐已经结束
尽管我们可以使用的 CPU 频率已经越来越高,以至于我们不少人称这是一个“CPU计算能力过剩的时代”。但是,事实恰恰相反。免费午餐已经结束,软件在历史性地向并发靠拢。
CPU性能提升在两年前就开始碰壁,但大多数人到了最近才有所觉察。大概在2003年初,一路高歌猛进的CPU时钟速度突然急刹车。受制于一些物理 学问题,如散热(发热量太大且难以驱散)、功耗(太高)以及泄漏问题等,时钟速度的提升已经越来越难。从单个CPU角度来讲,莫尔定律已经不再适用了。
接下来数年里,新型芯片的性能提升将主要从三个方面入手,其中仅有一个沿袭是过去的:
1、超线程
2、多核
3、缓存
软件在历史性地向并发靠拢
随着多核趋势的明朗,对软件来说,这意味一次巨变。多核时代,注定要改变计算机发展历史。在我们还在努力学习OO方法论时,须不知,一场新的颠覆性的编程革命到来了。
这场编程革命是什么呢?那就是“并行编程”。也许你会说,不就是“CreateThread和锁”吗,我已经会了。但这是完全不同的“并行编程”风格,我们可以称之为“无锁并行编程”,也可以称之为“基于异步消息传递的并行编程模型”。
这些和GC Allocator有什么关系?
内存管理是程序语言中的最基础的设施。如果你长期做服务端的开发,一定知道,服务器性能调优的关键在于内存管理。为什么GC Allocator是Lock Free(无锁)的?答案是:性能!
那么,为什么GC Allocator可以是Lock Free(无锁)的?原因在于,我们并不推荐你在两个进程之间Share彼此的内存(也就是说,不能在两个线程之间Share一个GC Allocator)。
以ScopeAlloc为例:
class Thread1{private: std::BlockPool& m_recycle; public: Thread1(std::BlockPool& recycle) : m_recycle(recycle) { } void operator()() { std::ScopeAlloc alloc1(m_recycle); ... }}; class Thread2{private: std::BlockPool& m_recycle; public: Thread2(std::BlockPool& recycle) : m_recycle(recycle) { } void operator()() { std::ScopeAlloc alloc2(m_recycle); ... }}; int main(){ std::BlockPool recycle; boost::thread thread1(Thread1(recycle)); boost::thread thread2(Thread2(recycle)); thread1.join(); thread2.join(); return 0;}
如该例子所示,我们推荐的实现方式是,每个线程有自己的私有内存分配器(GC Allocator),如上面的alloc1、alloc2。但他们可以共用同一个BlockPool。[Page]
BlockPool是线程安全的。之所以这样,是基于以下观念:
从逻辑的层次来讲,我们把组件分为两种,一种是系统级的,偏于计算机系统本身的抽象,如上面的BlockPool。它们从不直接由用户使用。所谓的 “无锁”编程,当然不是说不能用“锁”,只是把锁减少到最少。少到什么程度呢?少到只有系统级的组件才不得不用“锁”。而另一种组件是用户级的,偏于算法 逻辑的抽象,这类组件永远不要有“锁”。
现在,为什么GC Allocator没有锁的原因就很明了了:
内存分配器(GC Allocator)是用户级的概念,它只是算法逻辑的抽象。ScopeAlloc设计的妙处在于,把系统级的内存管理独立抽象到一个BlockPool类里,以将内存管理中的“锁”的代价减少到最低水平。
当然,两个GC Allocator也可以各自有自己的BlockPool,但是为了让一个Thread释放的内存可以立即被另一个Thread所使用,我们推荐你共享两个BlockPool。理论上,在一个应用程序中,只需要一个BlockPool实例是最好的。