数据对齐与代码优化笔记

1.

一直以来对数据对齐都不明白,直到看了这篇文章后才能说是有点感觉,结合代码将理解写下来,以备将来回头加深理解或者是修正错误的认知。

http://www.searchtb.com/2013/04/performance_optimization_tips.html

代码如下,运行的环境是64位的linux机器,内核3.10,gcc 4.8.2

/*************************************************************************
    > File Name: test_align.c
    > Author: dingzhengsheng
    > Mail: [email protected]
    > Created Time: 2015年04月22日 星期三 15时49分08秒
 ************************************************************************/

#include<stdio.h>
#include<stdint.h>
#include<time.h>
#include<stdlib.h>
#include <unistd.h>
#include<string.h>
#include<sys/time.h>
#include<sys/resource.h>

#define SECTOUSEC 1000000
#define ARR_LEN 10000000
#define OP |

//#define DZSTEST

#ifdef DZSTEST
#pragma pack (push)
#pragma pack (1)

struct notalignstruct
{
        char a;
        char b;
        char c;
        uint32_t d;
};
#pragma pack (pop)

struct alignstruct
{
        char a;
        char b;
        char c;
        uint32_t d;
};
#else

struct notalignstruct
{
        char a;
        uint32_t d;
        short int c;
};

struct alignstruct
{
        char a;
        short int c;
        uint32_t d;
};

#endif

void case_one(struct notalignstruct *array, uint32_t array_len, uint32_t *sum)
{
        uint32_t value = 1;
        uint32_t i;
        for(i=0; i<array_len; i++)
                value = value OP array[i].d;

        *sum = *sum OP value;
}
void case_two(struct alignstruct *array, uint32_t array_len, uint32_t *sum)
{
        uint32_t value = 1;
        uint32_t i;
        for(i=0; i<array_len; i++)
                value = value OP array[i].d;

        *sum = *sum OP value;
}

void case_two_yh1(struct alignstruct *array, uint32_t array_len, uint32_t *sum)
{
        uint32_t value = 1;
        uint32_t i;
        uint32_t length = array_len - (array_len&0x3);
        for(i=0; i<length; i+=4)
        {
                value = value OP array[i].d;
                value = value OP array[i+1].d;
                value = value OP array[i+2].d;
                value = value OP array[i+3].d;
        }

        for(; i<length; i+=4)
                value = value OP array[i].d;
        *sum = *sum OP value;
}

void case_two_yh2(struct alignstruct *array, uint32_t array_len, uint32_t *sum)
{
        uint32_t value = 1;
        uint32_t i;
        uint32_t length = array_len - (array_len&0x3);
        uint32_t value1,value2;

        for(i=0; i<length; i+=4)
        {
                value1 = array[i].d OP array[i+1].d;
                value2 = array[i+2].d OP array[i+3].d;
                value = value1 OP value2;
        }

        for(; i<length; i+=4)
                value = value OP array[i].d;
        *sum = *sum OP value;
}

void case_two_yh3(struct alignstruct *array, uint32_t array_len, uint32_t *sum)
{
        register uint32_t value = 1;
        register uint32_t i;
        uint32_t length = array_len - (array_len&0x3);
        uint32_t value1,value2;

        for(i=0; i<length; i+=4)
        {
                value1 = array[i].d OP array[i+1].d;
                value2 = array[i+2].d OP array[i+3].d;
                value = value1 OP value2;
        }

        for(; i<length; i+=4)
                value = value OP array[i].d;
        *sum = *sum OP value;
}

long int get_diff_time(struct timeval *tv1, struct timeval *tv2)
{

   long int n;

   n = tv2->tv_sec*SECTOUSEC + tv2->tv_usec - tv1->tv_sec*SECTOUSEC - tv1->tv_usec;
   return n;
}

void main()
{
        void *p;
        struct notalignstruct *array = malloc(sizeof(struct notalignstruct) * ARR_LEN);
        struct alignstruct *arr = malloc(sizeof(struct alignstruct) * ARR_LEN);
        uint32_t sum;
        struct timeval tv1,tv2;
        time_t timep;
        struct timezone tz;

        time(&timep);
        printf("数据对齐的影响比较:\n");
        gettimeofday(&tv1,NULL);
        case_one(array, ARR_LEN, &sum);
        gettimeofday(&tv2,NULL);
        printf("sizeof(not)=%u : %ld\n",sizeof(struct notalignstruct),get_diff_time(&tv1, &tv2));

        gettimeofday(&tv1,NULL);
        case_two(arr, ARR_LEN, &sum);
        gettimeofday(&tv2,NULL);
        printf("sizeof(not)=%u : %ld\n",sizeof(struct alignstruct),get_diff_time(&tv1, &tv2));

        printf("代码优化的性能比较:\n");
        gettimeofday(&tv1,NULL);
        case_two_yh1(arr, ARR_LEN, &sum);
        gettimeofday(&tv2,NULL);
        printf("减少cpu循环分支预测数sizeof(align)=%u : %ld\n",sizeof(struct alignstruct),get_diff_time(&tv1, &tv2));

        gettimeofday(&tv1,NULL);
        case_two_yh2(arr, ARR_LEN, &sum);
        gettimeofday(&tv2,NULL);
        printf("提高CPU指令流水线并行计算sizeof(align)=%u : %ld\n",sizeof(struct alignstruct),get_diff_time(&tv1, &tv2));

        gettimeofday(&tv1,NULL);
        case_two_yh3(arr, ARR_LEN, &sum);
        gettimeofday(&tv2,NULL);
        printf("将高频率的数据放入寄存器sizeof(align)=%u : %ld\n",sizeof(struct alignstruct),get_diff_time(&tv1, &tv2));

}

初始是想比较前两种不同的对齐方式的性能差距

#pragma pack (push)
#pragma pack (1)

struct notalignstruct
{
char a;
char b;
char c;
uint32_t d;
};
#pragma pack (pop)

struct alignstruct
{
char a;
char b;
char c;
uint32_t d;
};

原博文已经说的很清楚了,但是有一点我还有所怀疑就是如果编译器对notalignstruct数组分配的内存的起始地址不是2的N次方怎么办,那么对结构体中的变量d的访问是否仍然会导致两次取内存了,还是说编译器会自动解决这个问题吗?

运行的结果是

数据对齐的影响比较:
sizeof(not)=7 : 43064
sizeof(not)=8 : 38102
代码优化的性能比较:
减少cpu循环分支预测数sizeof(align)=8 : 26263
提高CPU指令流水线并行计算sizeof(align)=8 : 21551
将高频率的数据放入寄存器sizeof(align)=8 : 14229

差距还是挺大啊,然后再看这种情况:

struct notalignstruct
{
char a;
uint32_t d;
short int c;
};

struct alignstruct
{
char a;
short int c;
uint32_t d;
};

这个很明显,对齐的结果是前者会是12个字节的长度,后者是8个字节。程序运行前,我推测的结果是两者的速度应该是一致的,二者均按照4个字节对齐了。

但是结果还是出人意外:

数据对齐的影响比较:
sizeof(not)=12 : 40635
sizeof(not)=8 : 39052
代码优化的性能比较:
减少cpu循环分支预测数sizeof(align)=8 : 26475
提高CPU指令流水线并行计算sizeof(align)=8 : 21788
将高频率的数据放入寄存器sizeof(align)=8 : 14326
[[email protected] bug_test]$ ./test
数据对齐的影响比较:
sizeof(not)=12 : 40594
sizeof(not)=8 : 38828
代码优化的性能比较:
减少cpu循环分支预测数sizeof(align)=8 : 26262
提高CPU指令流水线并行计算sizeof(align)=8 : 21628
将高频率的数据放入寄存器sizeof(align)=8 : 14332

前者的速度小于后者,难道是因为前者占用的内存空间更大,实际物理内存跨页,碎片化更厉害导致访问更慢吗?

然后针对后面这两个结构体的计算函数case_two的优化就有意思了,博文中的这张图片就能说得很清楚了。实际上也很好理解,假如十个人连成一排从货车上已个个箱子里取一包包的货物传递到生产线上,而流水线的生产速度要大于工人传递的速度,而旁边有个检测师站在生成线的头上,会对每个箱子里的头一包货物拦住检查一下账本,将已收货物总数加1,并看是否在要用到的货物限额之内,如果检查到这包货物质量优良,那么整箱的货物都免检通过,否则就得丢掉这包货物,同时10个人手头上拿到的货物也得丢掉,case_two的情况就很明显,一个箱子里里面就放了一包货物,等于每一个都得停下来检查,大家都得在他检查的时候等着。

在我们自己就已经知道这个限额很大,是4XN 再加上零碎的三两包就够了,而我们检查的时候完全可以一个箱子里多放4个,每次只在箱子里第一包货物时做技术检查就可以了。

那这样就快很多了,流水线变得更加流畅了:

sizeof(not)=8 : 38828
代码优化的性能比较:
减少cpu循环分支预测数sizeof(align)=8 : 26262

然后了,工人说包都很轻,能不能一次就拿两个包来传递,货物上得更快了,这样让流水线减少空转的时间:

提高CPU指令流水线并行计算sizeof(align)=8 : 21628

然后检测师说我也能更快一点,收包数就不记到账本上一条一条的加了,我心头默记这样不久更快了嘛,省得翻账本费时间:

将高频率的数据放入寄存器sizeof(align)=8 : 14332

补充:再修改了一下,将length也改成register变量,执行速度更快了,只需要13464。

2.

现在来总结上面的技术与原理:

数据对齐就不说了,分支预测问题,上述的例子for循环实际上并不能说明问题。我推测的是如果是1000000万次for循环,循环体内是if(do something)else (do something)这种情况下性能的影响会非常明显。现代的cpu的分支预测能力已经很强了,像这种for循环的判断,我觉得成功率应该是接近100%的,如果是if那种,且判断的条件时刻都在变得情况,分支预测的成功率就可能会降到50%左右吧。我不知道分支预测的原理与算法,我推想的是这就如同猜硬币的正反一样,可能cpu能够根据当前的风向,当前扔的力度来猜测,历史数据,提高了准确度。内核代码中的likely和unlikely就是程序员告诉编译器哪个分支预测的可能性更高,那么编译器就将可能性高的代码编译的指令放在判断之后,访问的更快。

至于指令的并行处理,我的理解是减少执行的机器代码,取数据的动作还是两次,但是计算的动作已经只需要做一次了。这个理解我自己也是模模糊糊的,等待以后理解更清楚后补充吧。

对寄存器的数据操作是最快的,依次是一级缓存,二级缓存,三级缓存,内存,硬盘。寄存器的速度和cpu的处理速度是同频率的,cpu操作起来毫无延迟,而缓存则要高上一个档次,内存再高上一个档次,最慢的当然就是硬盘了,如果从硬盘取数据到内存再访问,那就慢的没边了。程序中变量value,i,length变量都是放在栈上操作(有可能某一个是放在寄存器中,没看汇编代码,只做推测),那么速度就会比寄存器慢好多,但是寄存器就那么十几个,变量多的时候不能大家都想上,如果将频繁访问的数据放入寄存器中操作,很明显就会在整体上提高处理的速度。最后优化的结果只用了原始的case_one的1/3时间。

时间: 2024-12-14 18:49:27

数据对齐与代码优化笔记的相关文章

数据对齐详解(转载)

数据对齐实际上是内存字节的对齐,今天偶然翻开自己以前做的笔记,发现做了好多的题,但现在对于我来说觉得很陌生.上网查了一下数据对齐的原因和方式,现在把它整理出来以备之后的学习复习巩固. 一.什么是数据对齐 1.现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐. 2.访问数据的地址要满足一定的条件,能被这个

gpu显存(全局内存)在使用时数据对齐的问题

全局存储器,即普通的显存,整个网格中的任意线程都能读写全局存储器的任意位置. 存取延时为400-600 clock cycles  非常容易成为性能瓶颈. 访问显存时,读取和存储必须对齐,宽度为4Byte.如果没有正确的对齐,读写将被编译器拆分为多次操作,降低访存性能. 多个warp的读写操作如果能够满足合并访问,则多次访存操作会被合并成一次完成.合并访问的条件,1.0和1.1的设备要求较严格,1.2及更高能力的设备上放宽了合并访问的条件. 1.2及其更高能力的设备支持对8 bit.16 bit

erlang shell表格数据对齐

最近在erlang shell做一些测试,为了让测试结果数据显得更直观,想对齐需要打印的数据,做成像表格一样的效果. 开始的想法是在数据中插入tab.当然,erlang也有对tab的支持,但实际效果不理想. 对tab的支持如下: 1> io:format("No1~sNo2~n1~s2~n", ["\t", "\t"]). No1        No2 1             2 ok 但数据的长度超过8个字符就有问题,那有没有一种可以

数据与计算机通信学习笔记

数据与计算机通信学习笔记---- OSI(Open Systems Interconnection--开放式系统互联(参考模型))是一个层的模型. 特点: 每一层提供一部分通信功能. 每一层依赖于上一层所提供的功能,并为下一高层提供服务. 一个层的改变不需要其他层的变化.     OSI模型示意图 物理层:关心在物理媒体上的无结构比特流的传输,处理机械的,电气的,功能的和过程化的特性,以接入物理媒体.(在此层工作的有转发器等设备.) 数据链路层:提供跨越物理层的可靠信息传递,携带必要的同步,差错

简单数据库数据导出工具总结笔记

简单界面: 1. 左边用ListBox控件,添加一个控件变量m_lb,将数据从数据库导出,然后用m_lb.AddString()添加到ListBox中显示. 在OnInitDialog()中加入.CDBBase是封装的mysql类,一开始从数据库导出的汉字显示乱码,因为数据库用的utf-8,而程序这边用的gbk, 后来加了pDBbase->DBexecute("SET NAMES 'GB2312'");显示正确. 获取ListBox中元素个数用m_lb.GetCount(),返回

PHP 数据库驱动、连接数据不同方式学习笔记

相关学习资料 http://www.php.net/manual/zh/refs.database.php http://www.php.net/manual/zh/internals2.pdo.php http://bbs.phpchina.com/thread-184537-1-1.html http://www.metsky.com/archives/660.html http://www.phpbuilder.com/ http://www.w3school.com.cn/php/php

sizeof数据对齐问题

#include <iostream> using namespace std; struct A1 { int a; static int b; }; struct A2 { int a; char c; }; struct A3 { float a; char c; }; struct A4 { float a; int b; char c; }; struct A5 { double d; float a; int b; char c; }; void main() { cout<

Ultra Edit中的数据对齐

有时会用到Ultra Edit的数据对齐功能.比如,要求64个符号一组,从低位开始对齐.这时,如果数据长度不是一行长度的整数, 就会产生高位对齐.低位不足的问题.为了调整,往往需要逐行调整,很不方便. 有一个优化的方法,先计算数据长度除以"行长度"的余数,然后在数据首位前补足零,以保证整除.之后自动对齐,没有低位不足 的问题.

gcc 数据对齐之:总结篇.

通过上面的分析,总结结构体对齐规则如下: 1.数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行.2.结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行.3.结合1.2颗推断:当#pragma p