非一致性内存访问模型与内存分配器

标签: 计算机与 Internet | 发表时间:2011-10-21 17:11 | 作者:snnn119 cong
分享到:
出处:http://snnn.sinaapp.com

CPU主频涨不上去了,一直停留在2-3G。前端总线的时钟频率也涨不上去了,我现在用的这个小黑,Intel Core2 P8600,前端总线的时钟频率只有266MHz。于是,虽然内存越来越便宜了,但是没有那么大的高速带宽来连接CPU和内存啊。

于是NUMA出现了。CPU组成node,每个node各自管理几十G内存,然后node和node之间通过Point-to-Point的方式建立高速直连。于是系统总线就没了,出现一个新名词,QPI,指那个快速访问通道,它不光连接内存和CPU,还连接其它外设如显卡。但是有个重要的结果是:CPU到每根内存条的“距离”是不相等的。有的是直连,所以速度很快,而有的需要绕到另一个node去拿,这样不仅速度慢,而且很容易把node之间的那个互连通道堵死。想想看,如果你工作在天津,但是偏偏要住在北京,每天上班下班是不是很痛苦?为什么呢?为什么会这样?为什么我想访问的这个内存不在我身边?那它在哪?

从C语言的malloc说起。首先强调一点,malloc分配的是“地址空间”,而不是内存!同理,free释放的也只是地址空间,而不是内存。当你访问某个内存地址,而这个地址没有映射到任何物理页的时候,就会发生缺页中断,然后此时,操作系统才分配内存。简单点说,”内存的分配发生在第一次访问的时候!“。

总的来说,内存分配器此时有三种策略:
1、糊里糊涂。什么都不知道,随便分。
2、就近,找离当前CPU最近的node分配。
3、round-robin。把你要的东西尽可能的平均分到每个node上。

我不清楚你用的C Runtime到底是哪一种实现,反正以上三种都有可能。喜欢C的人大多都是追求高效,那么自然喜欢第二种咯?于是就有人提出,服务器的启动过程应该做成并行化的,比如IO的buffer就让IO线程去初始化,各自做各自的。这样听起来很有道理,但是!亲爱的,如果这个线程被调度到另一个CPU上怎么办?所以我们不光得控制内存怎么分配的,还得控制线程调度策略,把这个线程绑在固定的CPU组上。更复杂的是,执行任务的是一个线程池怎么办?请问,我是在写Application,还是Operating System? 无论如何,“谁要用谁分配”依然是一个有效的优化策略。

那么说Java吧,它比较清晰。Java有4种垃圾回收策略 串行化、并行、CMS(并发)、G1。如果选择了并行化的垃圾回收策略(Server版的jvm默认如此),它的内存池分为三类:新生代、老生代、永生代。
并行化的垃圾回收器有一个开关,-XX:+UseNUMA,这个影响到新生代的Eden Space这个内存池的分配器的策略。先说Eden Space是干什么的:大部分new出来的对象都是首先在Eden Space上创建,这里面的对象都是临时创建而又立马被销毁的对象,否则会很快(小于1秒)就被挪到老生代里面去。如果打开了-XX:+UseNUMA,那么Eden Space会尽量就近找node分配,从而获得最经济、快捷的内存访问。可是另外,其它几个内存池呢?他们要么是采用的糊里糊涂的方式,要么就是采用的方式3,做round-robin。所以说,前面所说的为了内存访问局部化而把启动过程做成并行的对JAVA来说完全不适用。

那么,现在重新思考NUMA的问题:访问速度、带宽。
访问速度:的确,访问远处的内存会慢些,但是会慢多少呢?30%?50%?200%?你测过吗?我没有。据说,即便再慢,也比Nehalem之前的任何CPU快,因为改进了系统架构去掉北桥做了QPI等等。而且,根据我看到的各种数据来看,速度差距在50%以内。NUMA真是个很有意思的话题。我初次接触到这个概念是在《Solaris内核结构》那本书上,那本书是06年出版的。那个时代的硬件和现在差别很大的,那时候做NUMA可真是死了心的做,差2倍、差20倍它也觉得没问题,因为它和SMP相比增加了带宽嘛。于是OS就得被迫为这样巨大的差异做各种优化。可是现在呢?你看JVM,为什么仅仅对Eden Space做了就近分配?网上有很多关于NUMA的文章,以及如何在NUMA架构上写出更高效的程序。但是,很多观点是陈旧的。

带宽:你确实把CPU间的QPI跑满了吗?近年产的CPU,每条QPI的单向带宽是每秒12GB(理论最大值),如今看来,只有大型的数据库Server或者Cache Server才有可能把它跑满。否则,你不需要考虑带宽的问题。

所以现在有一种新的策略:SUMA,或者叫page interleaving,这个可以做到硬件层面去。把物理地址空间以均匀交替的方式分给每个node。例如0×0000-0x0FFF分给node 1,0×1000-0x1FFF分给node 2,以此类推。然后所有软件完全不在乎NUMA这件事情。做page interleaving的目的是尽可能平均的使用每个QPI通道。我觉得一般来说,这就足够了。Intel NUMA的x86 CPU在3年前才开始陆续面世,先让OS、compiler、jvm这些去适应它,等他们都折腾够了,最后才是我们这些普通的software developer。

相关 [内存 访问 模型] 推荐:

非一致性内存访问模型与内存分配器

- cong - snnn的blog
CPU主频涨不上去了,一直停留在2-3G. 前端总线的时钟频率也涨不上去了,我现在用的这个小黑,Intel Core2 P8600,前端总线的时钟频率只有266MHz. 于是,虽然内存越来越便宜了,但是没有那么大的高速带宽来连接CPU和内存啊. CPU组成node,每个node各自管理几十G内存,然后node和node之间通过Point-to-Point的方式建立高速直连.

Java 内存模型 JMM

- - 码蜂笔记
JMM,Java Memory Model,Java 内存模型. 什么是内存模型,要他何用. 假定一个线程为变量var赋值: var = 3;,内存模型要回答的问题是:在什么条件下,读取变量var的线程可以看到 3这个值. 如果缺少了同步,线程可能无法看到其他线程操作的结果. 导致这种情况的原因可以有:编译器生成指令的次序可以不同于源代码的“显然”版本,编译器还会把变量存储在寄存器而不是内存中;处理器可以乱序或并行执行指令;缓存会改变写入提交到主存得到变量的次序;存储在处理器本地缓存中的变量对其他处理器不可见 等等.

深入Java内存模型

- - ImportNew
你可以在网上找到一大堆资料让你了解JMM是什么东西,但大多在你看完后仍然会有很多疑问. happen-before是怎么工作的呢. 用volatile会导致缓存的丢弃吗. 为什么我们从一开始就需要内存模型. 通过这篇文章,读者可以学习到足以回答以上所有问题的知识. 它包含两大部分:第一部分是硬件层次的大体架构,第二部分是深入OpenJdk源代码和实现.

浅析C++多线程内存模型

- 2sin18 - 并行实验室 | Parallel Labs
注:本文发表于《程序员》2011年第6期并行编程专栏,略有删改. 在即将到来的C++1x标准中,一个重大的更新就是引入了C++多线程内存模型. 本文的主要目的在于介绍C++多线程内存模型涉及到的一些原理和概念,以帮助大家理解C++多线程内存模型的作用和意义. 顺序一致性模型(Sequential Consistency).

Java 多线程内存模型

- - ITeye博客
Java 多线程内存模型.       Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果. 在此之前,主流程序怨言(如C/C++等)直接使用物理硬件(或者说操作系统的内存模型),因此,会由于不同的平台上内存模型差异,导致程序在一套平台上并发完成正常,而在另一套平台上并发访问却经常出错,因此经常需要针对不同的平台来编写程序.

Java运行时的内存模型

- - CSDN博客编程语言推荐文章
每个线程单独的数据区(线程间不共享). 每个线程都有一片单独的内存区域,这里面包含:程序计数器(program counter register),JVM栈和本地方法栈(Native Method Stack). 当一个新的线程被创建的时候,这片内存就已经被分配出来了. 程序计数器:为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间的计数器互不影响,独立存储.

Java内存模型修订了!

- - 互联网 - ITeye博客
传统的Java内存模型涵盖了很多Java语言的语义保证. 在这篇文章中,我们将重点介绍其中的几个语义,以更深入地了解他们. 对于本文中描述的语义,我们还将尝试体会对现有Java内存模型更新的动机. 本文中与JMM未来更新相关的讨论,将被称为JMM9. 现有的Java内存模型,如JSR133(以下称为JMM-JSR133)中所定义的,为共享内存指定了一致性模型,并且有助于为开发者提供与JMM-JSR133表述一致的定义.

Nginx使用Linux内存加速静态文件访问

- - IT技术博客大学习
标签:   Nginx. Nginx是一个非常出色的静态资源web服务器. 如果你嫌它还不够快,可以把放在磁盘中的文件,映射到内存中,减少高并发下的磁盘IO. nginx.conf中所配置站点的路径是/home/wwwroot/res,站点所对应文件原始存储路径:/opt/web/res. shell脚本非常简单,思路就是拷贝资源文件到内存中,然后在把网站的静态文件链接指向到内存中即可.