失落的C语言结构体封装艺术
2014-02-13 00:17
477 查看
写在最前面: 这里的文章并非原文,是我自己的观后小结,所以简短并会省略一些内容。
在x86或ARM处理器上,基本的C数据类型的存储一般并不是起始于内存中的任意字节地址。除了字符外,其他每种类型都有对齐要求。
带符号与不带符号间没有差别。这个行话叫:在x86和ARM上,基本C语言的类型是自对齐(self-aligned).
数据与数据间。由于受他们起始位置的影响,数据与数据间存放的地址会有间隙,我们把这无用的地址(间隙)称为“水坑”。
在C语言里,结构体的地址与它第一个成员的地址是相同的——没有前置填充。注意:在C++里,看上去像结构体的类可能不遵守这个规则!(遵不遵守依赖于基类和虚拟内存函数如何实现,而且因编译器而不同。)
不但结构体内会存在自对齐和填充,结构体和结构体之间也会存在尾随填充(trailingpadding)和跨步地址(stride address)。这些都会造成结构体在内存中的浪费,因此重构结构体很重要,这也是结构体封装的艺术。
第一件需要注意的事情是,“水坑”仅发生于两个地方。一个是大数据类型(有更严格的对齐要求)的存储区域紧跟在一个较小的数据类型的存储区域之后。另一个是结构体自然结束于它的跨步地址之前,需要填充,以使下一个实例可以正确对齐。
消除“水坑”的最简单的方法是按对齐的降序来对结构体成员重排序。就是说:所有指针对齐的子域在前面,因为在64位的机器上,它们会有8字节。接下来是4字节的整型;然后是2字节的短整型;然后是字符域。
(2)可读性和缓存局部性:你应该做的事情是保持可读性——把相关的和同时访问的数据组合到毗邻的区域——这也会提高缓存行的局部性。这都是用代码的数据访问模式的意识,聪明地重排序的原因。
如果你的代码有多线程并发访问一个结构体,就会有第三个问题:缓存行反弹(cache line bouncing)。为了减少代价高昂的总线通信,你应该组织你的数据,使得在紧凑的循环中,从一条缓存行中读取,而在另一条缓存行中写。
是的,这与之前关于把相关数据组成同样大小的缓存行块的指南有些矛盾。多线程是困难的。缓存行反弹以及其它的多线程优化问题是十分高级的话题,需要整篇关于它们的教程。这里我能做的最好的就就是让你意识到这些问题的存在。
S. Raymond 翻译: 伯乐在线 - cjpan
译文链接: http://blog.jobbole.com/57822/
1.对齐和填充
在现代处理器上,你的C编译器在内存里对基本的C数据类型的存放方式是受约束的,为的是内存访问更快。在x86或ARM处理器上,基本的C数据类型的存储一般并不是起始于内存中的任意字节地址。除了字符外,其他每种类型都有对齐要求。
类型 | 起始地址 |
字符 | 任意 |
2字节短整型 | 起始于偶地址 |
4字节整型 | 起始于被4整除的地址 |
8字节长整型 | 起始于被8整除的地址 |
双精度浮点型 | 起始于被8整除的地址 |
浮点型 | 起始于被4整除的地址 |
带符号与不带符号间没有差别。这个行话叫:在x86和ARM上,基本C语言的类型是自对齐(self-aligned).
数据与数据间。由于受他们起始位置的影响,数据与数据间存放的地址会有间隙,我们把这无用的地址(间隙)称为“水坑”。
2.结构体对,填充和重构
总的来说,一个结构体实例会按照它最宽的标量成员对齐。编译器这样做,把它作为最简单的方式来保证所有成员是自对齐,为了快速访问的目的。在C语言里,结构体的地址与它第一个成员的地址是相同的——没有前置填充。注意:在C++里,看上去像结构体的类可能不遵守这个规则!(遵不遵守依赖于基类和虚拟内存函数如何实现,而且因编译器而不同。)
不但结构体内会存在自对齐和填充,结构体和结构体之间也会存在尾随填充(trailingpadding)和跨步地址(stride address)。这些都会造成结构体在内存中的浪费,因此重构结构体很重要,这也是结构体封装的艺术。
第一件需要注意的事情是,“水坑”仅发生于两个地方。一个是大数据类型(有更严格的对齐要求)的存储区域紧跟在一个较小的数据类型的存储区域之后。另一个是结构体自然结束于它的跨步地址之前,需要填充,以使下一个实例可以正确对齐。
消除“水坑”的最简单的方法是按对齐的降序来对结构体成员重排序。就是说:所有指针对齐的子域在前面,因为在64位的机器上,它们会有8字节。接下来是4字节的整型;然后是2字节的短整型;然后是字符域。
3.重构时的困难
(1)难以处理的标量问题:数据类型大小的不确定性。(可用sizeof()来检查存储大小)(2)可读性和缓存局部性:你应该做的事情是保持可读性——把相关的和同时访问的数据组合到毗邻的区域——这也会提高缓存行的局部性。这都是用代码的数据访问模式的意识,聪明地重排序的原因。
如果你的代码有多线程并发访问一个结构体,就会有第三个问题:缓存行反弹(cache line bouncing)。为了减少代价高昂的总线通信,你应该组织你的数据,使得在紧凑的循环中,从一条缓存行中读取,而在另一条缓存行中写。
是的,这与之前关于把相关数据组成同样大小的缓存行块的指南有些矛盾。多线程是困难的。缓存行反弹以及其它的多线程优化问题是十分高级的话题,需要整篇关于它们的教程。这里我能做的最好的就就是让你意识到这些问题的存在。
4. 转载资料
原文链接: EricS. Raymond 翻译: 伯乐在线 - cjpan
译文链接: http://blog.jobbole.com/57822/
相关文章推荐
- 失落的C语言结构体封装艺术
- 程序设计基石与实践系列之失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- (转)失落的C语言结构体封装艺术
- 失落的C语言结构体封装艺术
- 深入js的面向对象学习篇(封装是一门技术和艺术)——温故知新(二)
- 编程的艺术 之 封装一个验证码类(php)
- C语言结构体封装艺术
- 毕加索的艺术——Picasso,一个强大的Android图片下载缓存库,OkHttpUtils的使用,二次封装PicassoUtils实现微信精选
- Script.aculo.us开发系列(五):Prototype封装的艺术
- 毕加索的艺术——Picasso,一个强大的Android图片下载缓存库,OkHttpUtils的使用,二次封装PicassoUtils实现微信精选
- 系统集成之-2 封装Windows Server 2003 支持多服务部署