这章主要介绍c++语言中各种特性对性能的影响。
不同的变量存储位置
stack
众所周知,函数中的临时变量或对象一般存储在内存空间中的stack区。每当调用函数时,参数和临时变量进栈,当函数返回时,参数和临时变量出。s由于stack可被不断重复使用,栈是内存空间中最高效的存储方式。当临时变量中没有大对象时,访问栈上的临时变量也基本能用上L1 data cache.
global or static
在函数体之外声明的变量称之为global变量,可被任何函数访问。被static修饰的变量称为static变量。
global和static变量在程序运行期间会被放置于内存空间中的静态数据区。静态数据区域分为三个部分:一部分存储const类型的global/static变量,一部分存储已被初始化的global/static变量,最后一部分存储未被初始化的global/static变量
使用静态数据区的好处是,global/static变量在程序启动前就有专门的存储位置,坏处是在程序的生命周期内,这些存储位置将被一直占据,可能会降低data cache的效率。
所以建议尽量不要使用global变量
register
register变量存储在cpu寄存器中,函数中的临时变量特别适合放到register中。优点很明显,访问register变量比访问RAM快得多,但是cpu寄存器大小是非常有限的,在64位x86架构中,有:
- 14个整数寄存器
- 16个浮点寄存器
volatile
volatile用于声明一个变量可被其他线程改变,阻止编译器依赖变量始终具有代码中先前分配的值的假设来进行优化 。
volatile int seconds; // incremented every second by another thread void DelayFiveSeconds() { seconds = 0; while (seconds < 5) { // do nothing while seconds count to 5 } }
上面的代码如果不声明为volatile, 编译器将任务while条件一直成立,即使别的线程中改变了seconds的值。
thread-local
大多数编译器可以使用关键字 __thread
或 __declspec(thread)
来实现静态变量和全局变量的线程本地存储。这样的变 量对于每个线程都有一个实例 。
线程本地存储是低效的,因为它是通过存储在线程访问块中的指针进行访问的。因此建议尽量避免线程本地存储,代之以stack存储。
dynamic memory allocation
c++中通过new/malloc动态分配存储,通过delete/free释放动态分配的的存储,动态分配的存储放在内存空间的heap区中。
优点:使用相对stack存储更加灵活
缺点:动态分配和释放很耗时,容易出现野指针、悬垂指针、内存泄露、内存碎片等问题。
variables declared inside a class
类中声明的变量按照在类中的顺序存储,存储位置由类的对象在哪里定义的决定。static修饰的类成员变量将存储在静态数据区,只有一个实例。
将变量存储在类中的好处是保证了空间局部性,对cpu data cache更友好
整型变量和运算符
整数大小
对于不同的平台,不同整数类型(char/short/int/long)的大小可能不同。
无论大小如何,整数运算基本都很快,除非使用了大于cpu寄存器大小的类型,比如在32位系统中使用64位整数。
建议在与大小无关且没有溢出风险的情况下使用默认整数大小,例如简单变量、循环计数器等。 在大型数组中,为了更好地 使用数据缓存,最好使用对于特定用途来说足够大的最小整数大小。
无符号整形数 vs. 有符号整形数
在大多数情况下,使用有符号整数和无符号整数在速度上没有区别。 除了
- 除以常数:当你将一个整数除以一个常数时,无符号要快于有符号
- 对于大多数指令集,有符号整数比无符号整数转换成浮点数要快
- 有符号变量和无符号变量的溢出行为不同。
整数运算符
整数运算非常快。加减和位操作只需一个时钟周期,乘法需要3-4个时钟周期,除法需要40-80个。
自增和自减运算符
当仅用于递增整数变量时,使用递增前或递增后都没有区别。效果完全相同。例如,for (i=0; i<n; i++)和for (i=0; i<n; ++i)是一样的。但是当使用表达式的结果时,效率可能会有所不同。例如,x = array[i++] 比 x = array[++i] 速度更快,因为在后一种情况下,数组元素的地址的计算必须等待 i 的新值,这将使 x 的可用性延迟大约两个时钟周期。