缓存对齐与字节对齐_gcc 默认cacheline对齐吗-程序员宅基地

技术标签: linux操作系统  C++开发  oracle转载博文未看  windows操作系统  

缓存对齐与字节对齐  


一. Cache

【指的是CPU和内存间的那个高速缓存存储器,不是内存里一块做缓存用的区域】

Cache一般来说,需要关心以下几个方面

1)Cache hierarchy

Cache的层次,一般有L1, L2, L3 (L是level的意思)的cache。通常来说L1,L2是集成  在CPU里面的(可以称之为On-chip cache),而L3是放在CPU外面(可以称之为Off-chip cache)。当然这个不是绝对的,不同CPU的做法可能会不太一样。这里面应该还需要加上register,虽然register不是cache,但是把数据放到register里面是能够提高性能的。

2)Cache size

Cache的容量决定了有多少代码和数据可以放到Cache里面,有了Cache才有了竞争,才有了替换,才有了优化的空间。如果一个程序的热点(hotspot)已经完全填充了整Cache,那么再从Cache角度考虑优化就是白费力气了,巧妇难为无米之炊。我们优化程序的目标是把程序尽可能放到Cache里面,但是把程序写到能够占满整个Cache还是有一定难度的,这么大的一个Code path,相应的代码得有多少,代码逻辑肯定是相当的复杂(基本上是不可能,至少我没有见过)。

3)Cache line size

CPU从内存load数据是一次一个cache line;往内存里面写也是一次一个cache line,所以一个cache line里面的数据最好是读写分开,否则就会相互影响。

4)Cache associative

Cache的关联。有全关联(full associative),内存可以映射到任意一个Cache line;也有N-way关联,这个就是一个哈希表的结构,N就是冲突链的长度,超过了N,就需要替换。

5)Cache type

有I-cache(指令cache),D-cache(数据cache),TLB(MMU的cache),每一种又有L1,L2等等,有区分指令和数据的cache,也有不区分指令和数据的cache。

二. 代码层次的优化

1) 字节 alignment (字节对齐)

要理解字节对齐,首先得理解系统内存的组织结构. 把1个内存单元称为1个字节,字节再组成字,在8086时代,16位的机器中1字=2个字节=16bit,而80386以后的32位系统中,1字=4个字节。大多数计算机指令都是对字进行操作,如将两字相加等。也就是说,32位CPU的寄存器为32位,导致指令的操作对象是32位字;16位CPU的寄存器为16位,移动、加、减等指令的操作对象也是16位字。由于指令的原因,内存的寻址也同样是按字进行操作,在16位系统中,如果你访问的只是低8位,内存寻址还是按16位进行,然后再根据A0地址线选择低8位还是高8位,这一过程成为一次内存读(写),在16位系统中,如果读取一个32位数,要花费两个内存读周期(先读低16,再读高16)。同理32位CPU的内存寻址按4个单元进行。

为了达到高效的目的,在16位系统中,变量存储的起始地址是2的倍数,32位系统中,变量存储的起始地址是4的倍数,而这些工作都是由编译器来完成的。下面举个例子来说明这个问题。如下图所示: 缓存对齐与字节对齐 - CR7 - CR7的博客  

    上图是16位系统的内存布局图,深蓝色表示变量覆盖的内存范围,假设变量的大小为2个字节,变量的起始物理内存地址为0000H时,访问这个变量时,只需要一次内存的读写。然而,当变量的内存起始地址为0001H时,cpu将耗费两次读周期进行变量访问,具体过程如下:为了访问变量的低8位,cpu将通过寻址访问起始地址为0000H所在的字,然后找到当前字的高8位;随后cpu再访问0002H所处字的低8位,此低8位就是变量的高8位,这样经过cpu的拼装变量的访问就结束了,可见,需要经过两次读周期才能正确访问变量的值,效率是前者的1/2。

__attribute__((aligned(n)))表示所定义的变量为n字节对齐; 

字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) (结构体)变量的首地址能够被其(最宽)基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

__attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的.例如:

__attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。

__attribute__((aligned(n)))表示所定义的变量为n字节对齐; 

struct B{ char b;int a;short c;}; (默认4字节对齐)

这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是12。

下面我们使用预编译指令__attribute__((aligned(n)))来告诉编译器,使用我们指定的对齐值来取代缺省的:

struct C{char b;int a;short c;}; __attribute__((aligned(2)))

这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是8

struct D{ char b;int a;short c;}; __attribute__ ((packed))

       sizeof(struct C)值是8,sizeof(struct D)值为7。

字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) (结构体)变量的首地址能够被其(最宽)基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

2) Cache line alignment (cache对齐)

数据跨越两个cache line,就意味着两次load或者两次store。如果数据结构是cache line对齐的,  就有可能减少一次读写。数据结构的首地址cache line对齐,意味着可能有内存浪费(特别是  数组这样连续分配的数据结构),所以需要在空间和时间两方面权衡。

对于普通代码,内存边界对齐也是有好处的,可以降低高速缓存(Cache)和内存交换数据的次数。 主要问题是在于Cache本身是分成很多Cache-Line,每条Cache-Line具有一定的长度,比如一般来说L1 Cache每条Cache Line长度在32个字节或64个字节;而L2的会更大,比如64个字节或128个字节。用户每次访问地址空间中一个变量,如果不在Cache当中,那么就需要从内存中先将数据调入Cache中.
       比如现在有个变量 int x;占用4个字节,它的起始地址是0x1234567F;那么它占用的内存范围就在0x1234567F-0x12345682之间。如果现在Cache Line长度为32个字节,那么每次内存同Cache进行数据交换时,都必须取起始地址时32(0x20)倍数的内存位置开始的一段长度为32的内存同Cache Line进行交换.   比如0x1234567F落在范围0x12345660~0x1234567F上,但是0x12345680~0x12345682落在范围 0x12345680~0x1234569F上,也就是说,为了将4个字节的整数变量0x1234567F~0x12345682装入Cache,我们必 须调入两条Cache Line的数据。但是如果int x的起始地址按4的倍数对齐,比如是 0x1234567C~0x1234567F,那么必然会落在一条Cache Line上,所以每次访问变量x就最多只需要装入一条Cache Line的数据了。比如现在一般的malloc()函数,返回的内存地址会已经是8字节对齐的,这个就是为了能够让大部分程序有更好的性能。

1.  __attribute__((aligned(cache_line)))对齐实现;

    struct syn_str { ints_variable; };__attribute__((aligned(cache_line)));

2. 算法实现
  
   引子
    int a; 
    int size = 8;        <----> 1000(bin)
    计算a以size为倍数的下界数:

  就让这个数(要计算的这个数)表示成二进制时,最后三位为0就可以达到这个目标。只要下面这个数与a进行"与运算"就可以了:
  11111111 11111111 11111111 11111000
  而上面这个数实际下就是 ~(size - 1),可以将该数称为size的对齐掩码size_mask.
   计算a以size为倍数的上下界数:
  #define alignment_down(a, size) (a & (~(size-1)) )
  #define alignment_up(a, size)   ((a+size-1) & (~ (size-1)))
  注: 上界数的计算方法,如果要求出比a大的是不是需要加上8就可以了?可是如果a本身就是8的倍数,这样加8不就错了吗,所以在a基础上加上(size - 1), 然后与size的对齐掩码进行与运算.
  例如:
  a=0, size=8, 则alignment_down(a,size)=0, alignment_up(a,size)=0.
  a=6, size=8, 则alignment_down(a,size)=0, alignment_up(a,size)=8.
  a=8, size=8, 则alignment_down(a,size)=8, alignment_up(a,size)=8.
  a=14, size=8,则alignment_down(a,size)=8, alignment_up(a,size)=16.
  注:size应当为2的n次方, 即2, 4, 8, 16, 32, 64, 128, 256, 1024, 2048, 4096 ...
  
   实现例子:

    struct syn_str { int s_variable; };

    void *p = malloc ( sizeof (struct syn_str) + cache_line );

    syn_str *align_p=(syn_str*)((((int)p)+(cache_line-1))&~(cache_line-1);

3) Branch prediction (分支预测)

代码在内存里面是顺序排列的。对于分支程序来说,如果分支语句之后的代码有更大的执行几率,  那么就可以减少跳转,一般CPU都有指令预取功能,这样可以提高指令预取命中的几率。分支预测  用的就是likely/unlikely这样的宏,一般需要编译器的支持,这样做是静态的分支预测。现在也有  很多CPU支持在CPU内部保存执行过的分支指令的结果(分支指令的cache),所以静态的分支预测  就没有太多的意义。如果分支是有意义的,那么说明任何分支都会执行到,所以在特定情况下,静态 分支预测的结果并没有多好,而且likely/unlikely对代码有很大的侵害(影响可读性),所以一般不  推荐使用这个方法.

if(likely(value)) 等价于 if(value)

if(unlikely(value)) 也等价于 if(value)

也就是说 likely() 和 unlikely() 从阅读和理解代码的角度来看,是一样的!!!


这两个宏在内核中定义如下:

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

__builtin_expect() 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。

__builtin_expect((x),1) 表示 x 的值为真的可能性更大;
__builtin_expect((x),0) 表示 x 的值为假的可能性更大。

也就是说,使用 likely() ,执行 if 后面的语句 的机会更大,使用unlikely(),执行else 后面的语句的机会更大。
例如下面这段代码,作者就认为 prev 不等于 next 的可能性更大,

if (likely(prev != next)) {
       next->timestamp = now;
        ...
} else {
        ...;
}
通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。

下面以两个例子来加深这种理解:

第一个例子: example1.c

int testfun(int x)
{
   if(__builtin_expect(x, 0)) {
         ^^^--- We instruct the compiler, "else" block is more probable
     x = 5;
     x = x * x;
   } else {
       x = 6;
   }
   return x;
}
在这个例子中,我们认为 x 为0的可能性更大

编译以后,通过 objdump 来观察汇编指令,在我的 2.4 内核机器上,结果如下:

# gcc -O2 -c example1.c
# objdump -d example1.o

Disassembly of section .text:

00000000 <testfun>:
   0:   55             push   %ebp
   1:   89 e5            mov    %esp,%ebp
   3:   8b 45 08          mov    0x8(%ebp),%eax
   6:   85 c0            test   %eax,%eax
   8:   75 07            jne    11 <testfun+0x11>
   a:   b8 06 00 00 00        mov    $0x6,%eax
   f:   c9              leave
    10:   c3              ret
    11:   b8 19 00 00 00        mov    $0x19,%eax
    16:   eb f7            jmp    f <testfun+0xf>


可以看到,编译器使用的是 jne (不相等跳转)指令,并且 else block 中的代码紧跟在后面。

8:   75 07              jne    11 <testfun+0x11>
a:   b8 06 00 00 00          mov    $0x6,%eax


第二个例子: example2.c

int testfun(int x)
{
  if(__builtin_expect(x, 1)) {
           ^^^ --- We instruct the compiler, "if" block is more probable
       x = 5;
       x = x * x;
  } else {
       x = 6;
  }
       return x;
}
在这个例子中,我们认为 x 不为 0 的可能性更大

编译以后,通过 objdump 来观察汇编指令,在我2.4内核机器上,结果如下:

# gcc -O2 -c example2.c
# objdump -d example2.o


Disassembly of section .text:

00000000 <testfun>:
   0:   55                 push   %ebp
   1:   89 e5                mov    %esp,%ebp
   3:   8b 45 08              mov    0x8(%ebp),%eax
   6:   85 c0                test   %eax,%eax
   8:   74 07                je     11 <testfun+0x11>
   a:   b8 19 00 00 00             mov    $0x19,%eax
   f:   c9                 leave
10:   c3                   ret
11:   b8 06 00 00 00                 mov    $0x6,%eax
16:   eb f7                  jmp    f <testfun+0xf>


这次编译器使用的是 je (相等跳转)指令,并且 if block 中的代码紧跟在后面。

   8:   74 07                je     11 <testfun+0x11>
   a:   b8 19 00 00 00             mov    $0x19,%eax

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/haiross/article/details/38899019

智能推荐

利用Python实现多元伯努利事件的朴素贝叶斯分类器_python手写实现伯努利贝叶斯分类器-程序员宅基地

文章浏览阅读2.2k次。前言本篇博客所写的算法对应于吴恩达教授的机器学习教程里的多元伯努利事件模型的朴素贝叶斯。多元伯努利事件模型的Python代码#!/usr/bin/env python# -*- coding: utf-8 -*-# @Time : 2018/9/415:55# @Author : DaiPuWei# E-Mail : [email protected]#..._python手写实现伯努利贝叶斯分类器

ipmitool常用命令详解_ipmitool命令详解-程序员宅基地

文章浏览阅读3.2w次,点赞18次,收藏219次。ipmitool命令ipmitool –I [open|lan|lanplus] commandOpenIPMI接口,command有以下项: raw:发送一个原始的IPMI请求,并且打印回复信息。 Lan:配置网络(lan)信道(channel) chassis :查看底盘的状态和设置电源 event:向BMC发送一个已经定义的事件(event),可用于测试配置的SNMP是否成功 mc:查看MC(..._ipmitool命令详解

java求数组中元素最大值最小值及其下标等相关问题_java、 编写两个方法,分别求出数组中最大和最小元素的下标。如果这样的元素个数大-程序员宅基地

文章浏览阅读9.6k次,点赞9次,收藏39次。功能需求:遍历数组,并求出数组中元素的最大元素,最小元素,及其相应的索引等问题,要求用方法完成. 思路:分别创建不同的方法,然后再调用方法.代码展示:public class Array{ public static void main(String[] args){ int[] arr={13,45,7,3,9,468,4589,76,4}; //声明数组并赋值..._java、 编写两个方法,分别求出数组中最大和最小元素的下标。如果这样的元素个数大

Linux(Ubuntu)中对音频批量转换格式MP3转WAV/PCM转WAV_ubuntu批量mp3转wav命令-程序员宅基地

文章浏览阅读2.2k次。1、批量将MP3格式音频转换成WAV格式利用ffmpeg工具,统一处理成16bit ,小端编码,单通道,16KHZ采样率的wav音频格式。首先新建Mp3ToWav.sh 文件以路径/home/XXX下音频处理为例,编辑如下代码段:#!/bin/bashfolder=/home/XXXfor file in $(find "$folder" -type f -iname "*.mp3..._ubuntu批量mp3转wav命令

用python+graphviz/networkx画目录结构树状图_networkx画树状图-程序员宅基地

文章浏览阅读1.3w次,点赞2次,收藏22次。想着用python绘制某目录的树状图,一开始想到了用grapgviz,因为去年离职的时候整理文档,用graphviz画过代码调用结构图。graphviz有一门自己的语言DOT,dot很简单,加点加边设置属性就这点东西,而且有python接口。我在ubuntu下,先要安装graphviz软件,官网有deb包,然后python安装pygraphviz模块。目标功能是输入一个路径,输出该路径下的_networkx画树状图

【绿色求索T1设备资产通1.5单机版】适用于资产密集型企业管理_求索t1设备资产通系统(单机版)注册码-程序员宅基地

文章浏览阅读899次。绿色求索T1设备资产通 1.5 单机版 [企业管理高价值设备资产的使用情况]下载软件大小:5.56MB软件语言:简体中文软件类别:软件授权:免费软件更新时间:2013-08-03 07:44:00应用平台:Win2K,WinXP,Win2003,Vista,Win7绿色软件下么官方地址:系统之家官网求索T1设备资产通 1.5 单机版 _求索t1设备资产通系统(单机版)注册码

随便推点

【C++ 项目设计】深入JSON处理与项目实践:C++中的高效设计与应用-程序员宅基地

文章浏览阅读220次。在`JSONHandler`中,我们定义了几个核心组件:- **JSON Parser (JSON 解析器)**:负责读取和解析JSON数据。- **JSON Writer (JSON 写入器)**:负责将JSON数据写入文件或其他输出流。- **JSON Manipulator (JSON 操作器)**:提供了一系列方法来修改、查询和操作JSON数据。这三个组件是`JSONHandler`的基石,它们确保了数据的正确读取、写入和操作。

Algorithm Gossip (20) 阿姆斯壮数_actan算法 c++-程序员宅基地

文章浏览阅读543次。Algorithm Gossip: 阿姆斯壮数_actan算法 c++

php中大量数据如何优化,如何对PHP导出的海量数据进行优化-程序员宅基地

文章浏览阅读429次。本篇文章的主要主要讲述的是对PHP导出的海量数据进行优化,具有一定的参考价值,有需要的朋友可以看看。导出数据量很大的情况下,生成excel的内存需求非常庞大,服务器吃不消,这个时候考虑生成csv来解决问题,cvs读写性能比excel高。测试表student 数据(大家可以脚本插入300多万测数据。这里只给个简单的示例了)SET NAMES utf8mb4;SET FOREIGN_KEY_CHECK..._php大数据优化

有道云笔记怎么保存html,有道云笔记如何保存网页 有道笔记保存页面教程-程序员宅基地

文章浏览阅读905次。有道云笔记如何保存网页 有道笔记保存页面教程网页剪报功能支持哪些浏览器?IE,360安全,Firefox,Chrome,搜狗,遨游等主流浏览器。不能收藏网页,原因是没有安装浏览器剪报插件:②点击如下图部门网页剪报”立即体验“。③在弹出”有道云笔记网页剪报“网页对话框,点击如下图”添加到浏览器“。④然后在弹出”确认新增扩展程序“网页对话框中,点击”添加“即可。⑤现在,在浏览器右上角多了一个标记,只需..._有道云笔记装扩展

EasyUI 取得选中行数据-程序员宅基地

文章浏览阅读63次。转自:http://www.jeasyui.net/tutorial/23.html本实例演示如何取得选中行数据。数据网格(datagrid)组件包含两种方法来检索选中行数据:getSelected:取得第一个选中行数据,如果没有选中行,则返回 null,否则返回记录。getSelections:取得所有选中行数据,返回元素记录的数组数据。创建数据网格(DataGrid)&lt..._easyui 获取table选中的一行的值

云上武功秘籍(三)华为云上部署金蝶EAS Cloud_云上部署含带宽-程序员宅基地

文章浏览阅读1k次。每天琐事缠身,查错、维护、开接口?——不,你可以更加富有创造力!假期千里迢迢飞回公司机房处理一个小故障?——不,你可以更加自由高效!如果这就是你的写照,那为什么不选择上云呢?如果要上云,那为什么不选择华为云呢?云上秘籍第三弹——超详细、超全面的金蝶EAS Cloud部署教程来啦!负载均衡?WEB安全?一篇文章全部搞定!最后,请大家相信我们华为云生态 ISV团队的诚意和实力,谢谢!_云上部署含带宽

推荐文章

热门文章

相关标签