注:本文的内容已经过时。最重要的一点变化是:我们文章中提到BlockPool是可以在不同的Thread中共享的,这一点发生了变化,我们把BlockPool也做成线程一级了(BlockPool不再线程安全)。
---
许式伟
2008-3-5
引言
我们在前文已经引入了两个GC Allocator:
你可能已经注意到,这两个 GC Allocator 都是非线程安全的(确实有不少人向我反馈的这个“问题”)。不过,这其实是有意为之。下面我们解释为什么。
免费午餐已经结束
尽管我们可以使用的 CPU 频率已经越来越高,以至于我们不少人称这是一个“CPU计算能力过剩的时代”。但是,事实恰恰相反。免费午餐已经结束,软件在历史性地向并发靠拢。
CPU性能提升在两年前就开始碰壁,但大多数人到了最近才有所觉察。大概在2003年初,一路高歌猛进的CPU时钟速度突然急刹车。受制于一些物理 学问题,如散热(发热量太大且难以驱散)、功耗(太高)以及泄漏问题等,时钟速度的提升已经越来越难。从单个CPU角度来讲,莫尔定律已经不再适用了。
接下来数年里,新型芯片的性能提升将主要从三个方面入手,其中仅有一个沿袭是过去的:
1、超线程
2、多核
3、缓存
软件在历史性地向并发靠拢
随着多核趋势的明朗,对软件来说,这意味着一次巨变。多核时代,注定要改变计算机发展历史。在我们还在努力学习OO方法论时,须不知,一场新的颠覆性的编程革命到来了。
这场编程革命是什么呢?那就是“并行编程”。也许你会说,不就是“CreateThread和锁”吗,我已经会了。但这是完全不同的“并行编程”风格,我们可以称之为“无锁并行编程”,也可以称之为“基于异步消息传递的并行编程模型”。
这些和GC Allocator有什么关系?
内存管理是程序语言中的最基础的设施。如果你长期做服务端的开发,一定知道,服务器性能调优的关键在于内存管理。为什么GC Allocator是No Lock(无锁)的?答案是:性能!
那么,为什么GC Allocator可以是No Lock(无锁)的?原因在于,我们并不推荐你在两个进程之间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。
BlockPool是线程安全的。之所以这样,是基于以下观念:
从逻辑的层次来讲,我们把组件分为两种,一种是系统级的,偏于计算机系统本身的抽象,如上面的BlockPool。它们从不直接由用户使用。所谓的 “无锁”编程,当然不是说不能用“锁”,只是把锁减少到最少。少到什么程度呢?少到只有系统级的组件才不得不用“锁”。而另一种组件是用户级的,偏于算法 逻辑的抽象,这类组件永远不要有“锁”。
现在,为什么GC Allocator没有锁的原因就很明了了:
内存分配器(GC Allocator)是用户级的概念,它只是算法逻辑的抽象。ScopeAlloc设计的妙处在于,把系统级的内存管理独立抽象到一个BlockPool类里,以将内存管理中的“锁”的代价减少到最低水平。
当然,两个GC Allocator也可以各自有自己的BlockPool,但是为了让一个Thread释放的内存可以立即被另一个Thread所使用,我们推荐你共享两个BlockPool。理论上,在一个应用程序中,只需要一个BlockPool实例是最好的。
参考:性能对比
分享到:
相关推荐
《侯捷 - C++内存管理机制_60_侯捷》31.G4.9pull allocator运行观察
《C++内存管理机制_60_侯捷》15.Macro for static allocator
《C++内存管理机制_60_侯捷》13.Per-class allocator 2
《深入理解计算机系统》课程的实验5材料 解答过程在:http://blog.csdn.net/u010560443/article/details/50611251
《C++内存管理机制_60_侯捷》14.Static allocator
第一講:Primitives 第二講:std::allocator 第三講:malloc/free 第四講:loki::allocator 第五講:其他主題 第
linux下c++ allocator 共享内存,内存池实现
std::allocator 是 C++标准库中提供的默认分配器,他的特点就在于我们在 使用 new 来申请内存构造新对象的时候,势必要调用类对象的默认构造函数 而使用 std::allocator 则可以将内存分配和对象的构造这两部分...
The Slab Allocator An Object-Caching Kernel Memory Allocator
目录记忆池此仅标头的C ++ 17库提供了一种简单的一致的无锁的单个类型元素的内存池的实现。 元素类型作为第一个模板参数传递。细节界面 template < typename xss=removed xss=removed> >class MemoryPool final{...
Malloc Lab: Writing a Dynamic Storage Allocator
这是一个通用的内存管理库。 该库的主要重点围绕该库中的内存分配器,这些内存分配器被设计为尽可能轻巧高效。 自定义C ++逻辑概念是在此库中严格设计和遵循的。 遗憾的是,由于它们尚未标准化,因此还不是C++20或...
ACE_Static_Allocator,管理固定大小的内存;ACE_Cached_Allocator,预先分配内存池,其中含有特定数目和大小的内存chunk;ACE_New_Allocator,为C++ new和delete操作符提供包装的分配器,内部使用new和delete操作符
在大部分系统语言中,比如 C 和 C++,您必须进行内存管理。本文将介绍手工的、半手工的以及自动的内存管理实践的基本概念。 追溯到在 Apple II 上进行汇编语言编程的时代,那时内存管理还不是个大问题。您实际上在...
通过替代STL std :: allocator的固定块来防止堆碎片错误并提高执行速度
文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半手工地管理内存,以及如何使用垃圾收集自动管理内存。 为什么必须管理内存 内存管理是计算机编程最为基本的...
《内存管理机制》55.bitmap_allocator(下)
对于这个项目,我已经实现了不同的方式来管理自己在 C++ 中的动态内存。这意味着我们将使用自定义内存分配器,而不是使用像“malloc”或“free”这样的本机调用,它会为我们做到这一点但以更有效的方式。 因此,...
想象一下返回Box, InlineStorage> :抽象类型,动态分配,没有内存分配。 想象一下存储Box<dyn> :您可以构建任务队列,而看不到内存分配。 想象一下创建一个const LOOKUP: BTreeMap<K, V, I
gpu-allocator = " 0.6.0 " 设置Vulkan的分配器 use ash :: version :: {DeviceV1_0, EntryV1_0, InstanceV1_0}; use ash :: vk; let mut allocator = VulkanAllocator :: new ( & VulkanAllocatorCreateDesc { ...