C++ Low level performance optimize

1.  May I have 1 bit ?

下面两段代码,哪一个占用空间更少,那个速度更快?思考10秒再继续往下看:)

//v1
struct BitBool
{
bool b0 :;
bool b1 :;
bool b2 :;
}
BitBool bb;
bb.b1 = true; //v2
struct NormalBool
{
bool b0;
bool b1;
bool b2;
} NormalBool nb;
nb.b1 = true;

第一种通常被认为是优化的版本,甚至UE3里都有很多类似代码,但实际上却在两方面都不占优,why?原因是现代大部分cpu都没有指令能直接访问1bit数据,而

bb.b1 = true

相当于

bb.b1 |= (1<<xxxx); 相应汇编代码可能为(实际汇编根据编译器可能会不同):

shl         cl,2 
xor         cl,al 
and         cl,4 
xor         al,cl

nb.b1 = true;只需要
mov BYTE PTR [rcx+2], dl

当考虑空间优化时一般只考虑到了数据占用空间,而没有考虑代码所占用的内存。因此虽然sizeof(BitBool)==1==8bit (默认8bit对齐的系统),但访问b1的指令所占空间却需要8~11byte! 考虑到访问成员的代码通常会成为内联函数,因此BitBool所占空间为 1 + 11 * n ,n为代码中需要访问数据的次数! 而NormalBool虽然需要3byte,但其访问代码只需要3byte机器码,所占空间为3 + 3 * n。因此无论在性能还是空间性,NormalBool均更好!

 2  Cache missing is killer!!!

把一个随机数列依次插入list和vector,保持两个新数列从小到大排序:7,5,2,7,9,3  ===>   2,3,5,7,7,9 ,下面是代码,对于不同的数据量n,哪一种方法更好?

std::list<int> myList;
std::vector<int> myVec; //create a random number array
const int size = ;
std::array<int, size> myArr;
for(int i = ; i < size; i++)
{
myArr[i] = rand() % ;
} //pre-allocate memory
myVec.reserve(size);
myList.resize(size,); //fill vector
for(int i = ; i < size; i++)
{
int value = myArr[i];
auto it = myVec.begin();
for(; it != myVec.end(); it++)
{
if(*it > value)
{
it = myVec.insert(it, value);
break;
}
}
if(it == myVec.end())
myVec.push_back(value);
} //fill list
for(int i = ; i < size; i++)
{
int value = myArr[i];
auto it = myList.begin();
for(; it != myList.end(); it++)
{
if(*it > value)
{
it = myList.insert(it, value);
break;
}
}
if(it == myList.end())
myList.push_back(value);
}

两段代码几乎相同从算法分析的角度看,频繁插入操作是vector的灾难,但实际测试结论是无论size为多大,vector总是比list快,并且size越大,差距越明显,在我的机器上当size=50k时快了近10倍!!why?首先list需要占用更多内存,其次vector总是保证元素位于连续的内存,这是最重要的!Cache missing导致的性能损失甚至比复制元素还严重。对现代CPU来说,运算速度已经非常快,一次cache missing就会浪费n个cpu周期,合理组织数据,让cpu减少等待时间是现代cpu非常重要的优化手段。

注意,上面的演示代码只是为了展示cache missing的重要性,并不是完成这个任务的最优方法,另外实际情况下对于复杂类型来说,随着复制代价的提高,vector未必就能总胜出了:)。

 3. False Sharing(cache-line ping-ponging)

大部分程序员都听说过cache missing,但知道false sharing的就不那么多了。为了讨论false sharing,首先要介绍cache line. 就像CPU不能读取1bit一样,cpu访问cache时通常也会多读取一些额外数据,同时读取的这一段数据就称为一条cache line。每一级cache都由n条cache line组成,对于intel i级的cpu来说cache line大小为64byte。假设cpu需要访问变量v时, v地址附近的数据都被读入cache中。对于单核的世界来说,一切都很好,但对于多核心下的多线程设计来说,问题就来了,假设v被加载到了第一个核的cache中,此时另一个核心需要访问临近v的变量v1怎么办? 如果都是只读操作,那么每个核心可以各保存一份cache line v的副本,不会有冲突。但如果线程1需要修改v,线程2需要修改v2怎么办呢,显然会导致不同核心的cache line状态不一致。为了解决这个问题,整个cache line都要被来回重新加载。比如:线程1从主内存加载cache line v,修改v,把整个cache line回写到主内存,线程2再重复这个过程修改v1,这种情况就称为false sharing,显然如果在并行运行的核心代码中出现这种的情况,性能是非常糟糕的,而这样的代码通常又不太容易发现

下面是wiki上false sharing的一个例子:

struct foo
{
int x;
int y;
}; static struct foo f; int sum_a(void)
{
int s = ;
for (int i = ; i < ; ++i)
s += f.x;
return s;
} void inc_b(void)
{
int i;
for (i = ; i < ; ++i)
++f.y;
}

假设sum_a和inc_b两个函数同事运行在不同核心的不同线程上,f的所有成员都在同一条cache line,inc_b在不听修改内存中的值,因此导致false sharing。

 

  在做性能优化前,一定要先profile,profile,profile!!!很多情况下,问题所在的位置和程序员所预期的都不一样,盲目修改代码甚至有可能降低程序性能!!!

ps:最悲剧的情况就是没有任何可靠的profile工具,还必须做性能优化,我目前的情况就是这样,怎一个惨字了得....

more refereence:
Modern C++: What You Need to Know
Native Code Performance on Modern CPUs: A Changing Landscape
Native Code Performance and Memory: The Elephant in the CPU

C++ Low level performance optimize的更多相关文章

  1. C++ Low level performance optimize 2

    C++ Low level performance optimize 2 上一篇 文章讨论了一些底层代码的优化技巧,本文继续讨论一些相关的内容. 首先,上一篇文章讨论cache missing的重要性 ...

  2. Solr实现Low Level查询解析(QParser)

    Solr实现Low Level查询解析(QParser) Solr基于Lucene提供了方便的查询解析和搜索服务器的功能,可以以插件的方式集成,非常容易的扩展我们自己需要的查询解析方式.其中,Solr ...

  3. zabbix监控redis多实例(low level discovery)

    对于多实例部署的tomcat.redis等应用,可以利用zabbix的low level discovery功能来实现监控,减少重复操作.  注:Zabbix版本: Zabbix 3.0.2 一.服务 ...

  4. 使用Java Low Level REST Client操作elasticsearch

    Java REST客户端有两种风格: Java低级别REST客户端(Java Low Level REST Client,以后都简称低级客户端算了,难得码字):Elasticsearch的官方low- ...

  5. Zabbix监控Low level discovery实时监控网站URL状态

    今天我们来聊一聊Low level discovery这个功能,我们为什么要用到loe level discovery这个功能呢? 很多时候,在使用zabbix监控一些东西,需要对类似于Itens进行 ...

  6. ChibiOS/RT 2.6.9 CAN Low Level Driver for STM32

    /* ChibiOS - Copyright (C) 2006..2015 Giovanni Di Sirio Licensed under the Apache License, Version 2 ...

  7. Consumer设计-high/low Level Consumer

    1 Producer和Consumer的数据推送拉取方式   Producer Producer通过主动Push的方式将消息发布到Broker n Consumer Consumer通过Pull从Br ...

  8. zabbix(10)自动发现规则(low level discovery)

    1.概念 在配置Iterms的过程中,有时候需要对类似的Iterms进行添加,这些Iterms具有共同的特征,表现为某些特定的参数是变量,而其他设置都是一样的,例如:一个程序有多个端口,而需要对端口配 ...

  9. Elasticsearch java api操作(一)(Java Low Level Rest Client)

    一.说明: 一.Elasticsearch提供了两个JAVA REST Client版本: 1.java low level rest client: 低级别的rest客户端,通过http与集群交互, ...

随机推荐

  1. [51单片机] EEPROM 24c02 + 数码管 + 中断 [统计开机次数]

    >_<:24c02的SCL连P2.0;SDA连P2.1;WP接GND;P0接8位数码管的8针;P2.2连段码;P2.3连位码; >_<:delay.c #include &qu ...

  2. 使用UEditor无法SetContent的问题

    无法SetContent是因为 <script id="txtContent" name="txtContent" type="text/pla ...

  3. jQuery 消息提示/通知插件

    常见消息提醒,类似于Chrome notification,易于使用,用户体验赞. // Simple $.sticky('hi, every body rock!'); // Advantage $ ...

  4. atitit.架构设计---方法调用结果使用异常还是返回值

    atitit.架构设计---方法调用结果使用异常还是返回值 1. 应该返回BOOL类型还是异常 1 2. 最终会有四种状况,抛出异常.返回特殊值.阻塞.超时 1 3. 异常的优缺点点 1 4. jav ...

  5. paip.提升效率--数据绑定到table原理和流程Angular js jquery实现

    paip.提升效率--数据绑定到table原理和流程Angular js  jquery实现 html #--keyword 1 #---原理和流程 1 #----jq实现的代码 1 #-----An ...

  6. Spring3.2.4集成quartz2.2.1定时任务(demo).

    在JavaEE系统中,我们会经常用到定时任务,下面是我自己写的一个demo. 前面几篇quartz博客感觉看的还是难懂 于是重新整理下 源码地址:http://pan.baidu.com/s/1BXH ...

  7. UWP开发-二维变换以及三维变换

    在开发中,由于某些需求,我们可能需要做一些平移,缩放,旋转甚至三维变换,所以我来讲讲在UWP中这些变换的实现方法. 一. 二维变换: UIElement.RenderTransform a.Trans ...

  8. Hibernate入门5持久化对象关系和批量处理技术

    Hibernate入门5持久化对象关系和批量处理技术 20131128 代码下载 链接: http://pan.baidu.com/s/1Ccuup 密码: vqlv 前言: 前面学习了Hiberna ...

  9. C++Builder RAD Studio XE, UTF-8 String 转换为 char * 字符串的最简单方式, 常用于sqlite3开发

    前段时间突然使用sqlite3开发,中间需要用中文,XE的缺省char*直接使用中文,在sqlite *.db3的数据库表格中显示是乱码,用数据库管理器来浏览等管理时非常不便. 于是决定还是使用utf ...

  10. Clojure上手

    Clojure,这是什么鬼?一门基于JVM(现在也有基于.NET CLR的了:Clojure CLR) 的函数式编程语言.在JVM平台运行的时候,会被编译为JVM的字节码进行运算..为什么要学它?其设 ...