C++元素的效率差异
类型转换
在C++语法中,有几种方式进行类型转换:
cpp
// Example 7.19
int i; float f;
f = i; // Implicit type conversion
f = (float)i; // C-style type casting
f = float(i); // Constructor-style type casting
f = static_cast<float>(i); // C++ casting operator
这些不同的方法有完全相同的效果。使用哪种方法是一个编程风格的问题。下边讨论下不同转换的时间损耗。
signed与unsigned转换
。。。
有符号与无符号整数间的转换只是让编译器以不同的方式解释整数的比特。不检查溢出,代码不需要额外时间。
整数大小转换
cpp
int i; short int s;
i = s;
一个整数转换成一个位数更长的整数时,如果是有符号的,通过扩展符号位,如果是无符号的,通过零扩展。如果是一个算术表达式的结果进行,通常需要1时钟周期。如果从内存读取一个变量的值来转换,通常不需要额外时间,如下:
。。。
将整数转换到更小的类型仅仅是忽略高位比特,不检查溢出。例如:
。。。
这个转换不需要额外时间。它只是保存32位整数的低16位。
浮点精度转换
在使用浮点寄存器栈时,float、double与long double间的转换不需要额外时间。在使用XMM寄存器时,需要2到15时钟周期(取决于处理器)。例子:
cpp
// Example 7.24
float a; double b;
a += b;
在这个例子中,如果使用XMM寄存器,转换是相对低效的。a与b应该是相同类型以避免。
整数到浮点转换
有符号整数到float或double的转换需要4 ~ 6时钟周期,取决于处理器与使用的寄存器类型。无符号整数的转换需要更长时间,除非AVX512指令集可用(AVX512DQ用于64bit无符号整数)。如果没有溢出的危险,首先把无符号整数转换到有符号整数会更快的:
。。。
浮点到整数转换
浮点值到整数的转换需要非常长的时间,除非启用SSE2或更新的指令集。通常,转换需要50 ~ 100时钟周期。原因是C/C++标准指定截断,因此浮点取整模式必须改变为截断,再改回来。
如果在代码的关键部分存在浮点到整数转换,那么对进行优化是重要的。可能的方案有:
- 使用不同类型的变量,避免转换。
- 将中间结果保存为浮点类型,将转换移出最里层循环。
。。。
指针类型转换
指针可以被转换到另一个类型的指针。类似的,指针可以转换到整数,或者整数可以转换到指针。整数有足够的bit位保存指针是重要的。
这些转换不会产生额外的代码。它只是以不同的方式解释相同比特,或者绕过语法检查。
当然,这些转换不安全。确保结果有效是程序员的责任。
重新解释对象的类型
通过转换地址类型,使编译器将一个变量或对象当做另一个类型是可能的:
cpp
float x;
*(int*)&x |= 0x80000000; // Set sign bit of x
这里,语法看起来有些奇怪。x 的地址被类型转换为一个整数指针,然后通过把x当做整数访问。实际上制作一个指针,编译器不产生任何额外的代码真正创造一个指针。这个指针只是被优化掉,结果x被处理为一个整数。但 & 操作符强制编译器在内存而不是寄存器里保存x。上面例子通过使用只能应用于整数的 | 操作符设置 x 的符号位。它比x = -abs( x );
更快。
在类型转换指针时,要小心一些风险:
- 违反严格的标准C别名规则,尤其是不同类型的两个指针不能指向相同的对象(除了char指针)。优化编译器可能在两个不同的寄存器中保存浮点与整数表示。你需要检查编译器的行为是否就是你所期望的。使用联合更安全。
- 如果对象被当做比其实际更大的类型对待,该技巧会无效。上面这个代码将出错,如果int比float使用更多比特。(在x86系统里,两者都使用32比特)。
。。。
const_cast
const_cast用于去除一个指针的const限制。它有一些语法检查,因此,比C风格的类型转换更安全,无需添加任何额外的代码。例如:
。。。
static_cast
static_cast操作符做的与C形式的类型转换相同。
reinterpret_cast
reinterpret_cast操作符用于指针转换。与c风格的转换类似,但会进行一些语法检查,不产生任何额外的代码。
dynamic_cast
dynamic_cast操作符用于将一个类指针转换为另一个类的指针。它对转换的有效性进行运行时检查。例如,在一个基类指针被转换为派生类的指针时,它检查原始指针是否真的指向派生类的一个对象。这个检查使得dynamic_cast比简单的类型转换更耗时些,但也更安全。它可能捕捉到原本没发现的编程错误。
转换类对象
涉及类对象的转换(而不是对象指针)是看可能的,只要程序员定义了说明如何进行这个转换的一个构造函数、一个重载赋值操作符或一个重载类型转换操作符。构造函数或重载操作符与成员函数效率相同。
欢迎交流