用于高性能加法和乘法的常量形式
Forms of constants for high performance addition and multiplication for double
我需要在循环中有效地将一些常量添加或乘以double类型的结果,以防止下溢。例如,如果我们有int,那么乘以2的幂将很快,因为编译器将使用移位。对于有效的double
加法和乘法,有一种常数形式吗?
编辑:似乎没有多少人理解我的问题,为我的草率道歉。我将添加一些代码。如果a
是int,这(乘以2的幂)将是更有效的
int a = 1;
for(...)
for(...)
a *= somefunction() * 1024;
与1024被例如1023替换时相比。不确定如果我们想添加到int中,什么是最好的,但我对此不感兴趣。我对a
是二重的情况感兴趣。常数(例如2的幂)的形式是什么,我们可以有效地将和相乘到一个二重?常数是任意的,只需要足够大就可以防止下溢。
这可能不仅限于C和C++,但我不知道还有什么更合适的标签。
在大多数现代处理器上,简单地乘以2的幂(例如,x *= 0x1p10;
乘以210或x *= 0x1p-10;
除以210)将是快速且无错误的(除非结果大到足以溢出或小到足以下溢)。
有些处理器具有用于某些浮点运算的"早期输出"功能。也就是说,当某些位为零或满足其他标准时,它们会更快地完成指令。然而,浮点加法、减法和乘法通常在大约四个CPU周期内执行,因此即使没有早期输出,它们也相当快。此外,大多数现代处理器一次执行多条指令,因此在乘法发生的同时,其他工作也在进行,并且它们是流水线式的,因此,通常情况下,在每个CPU周期中可以开始(和完成)一次乘法。(有时更多。)
二次幂相乘没有舍入误差,因为有效位(值的小数部分)不会改变,所以新的有效位是可以精确表示的。(除非乘以小于1的值,有效位的位可以被推到低于浮点类型的限制,从而导致下溢。对于常见的IEEE 754双格式,只有当值小于0x1p-1022时才会发生这种情况。)
不要将分割用于缩放(或用于反转先前缩放的效果)。相反,乘以倒数。(要删除以前的0x1p57缩放,请乘以0x1p-57。)这是因为除法指令在大多数现代处理器上都很慢。例如,30次循环并不罕见。
首先在并集中获得双精度,并选择"范围"和的"指数"指数"或的"范围"后一个尾数位。
union int_add_to_double
{
double this_is_your_double_precision_float;
struct your_bit_representation_of_double
{
int range_bit:53;//you can shift this to make range effect
//i dont know which is mantissa bit. maybe it is first of range_bit. google it.
int exponent_bit:10; //exponential effect
int sign_bit:1; //take negative or positive
}dont_forget_struct_name;
}and_a_union_name;
浮点加法和乘法在现代处理器中通常需要几个周期。
也许你应该退一步思考一下算法在做什么。在您的示例中,您有一个双嵌套循环。。。这意味着"somefunction()"可能会被多次调用。"双"的常见表示形式是IEEE,它使用11位作为指数,52位作为尾数(53位实际上是因为除了零之外还有隐含的"1")。这意味着你可以表示从非常小到非常大的53位精度的数字-二进制"浮点"可以将1024(2^10)位移动到数字"1.0"的左侧或右侧……如果"somefunction()"被调用一千次,并且它总是返回一个小于或等于0.5的数字,则下溢(每次乘以0.5,则将数字截断为"a"对折,表示将二进制浮点向左移动。在x86上,你可以通过在控制寄存器中设置一位来告诉处理器"将非标准化清除为零"——没有可移植的编程接口可以做到这一点,使用gcc可以使用
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
告诉处理器将非标准化刷新为零将使代码运行得更快,因为处理器不会试图表示超出(小于)法线(子法线或非标准化)的数字。面对生成次法线的算法(这会导致精度损失),您似乎在努力保持精度。如何最好地处理这一问题取决于您是否控制"somefunction()"。如果你确实控制了这个函数,那么你可以将它返回的值"标准化"为范围内的值
0.5 <= X <= 2.0
换句话说,返回以1.0为中心的值,并单独跟踪2的幂,需要乘以最终答案才能正确缩放。
如果您使用SSE,将常数直接添加到指数字段是一个合法的技巧(在FPU代码中,这非常可怕)-它通常具有两倍的吞吐量和4倍的延迟(具有float->int和/或int->float惩罚的处理器除外)。但是,既然这样做只是为了防止非标准化,为什么不打开FTZ(刷新为零)和DAZ(非标准化为零)呢?
您可以使用标准的frexp/ldexp函数将IEE 754值分解为其组件:
http://www.cplusplus.com/reference/clibrary/cmath/frexp/
http://www.cplusplus.com/reference/clibrary/cmath/ldexp/
这里有一个简单的示例代码:
#include <cmath>
#include <iostream>
int main ()
{
double value = 5.4321;
int exponent;
double significand = frexp (value , &exponent);
double result = ldexp (significand , exponent+1);
std::cout << value << " -> " << result << "n";
return 0;
}
执行涉及:http://ideone.com/r3GBy
在千兆赫处理器上,通过优化这种方式(移位与算术),您可以节省1或2纳秒。然而,从存储器加载和存储所需的时间大约为100纳秒,而到磁盘的时间为10毫秒。与优化缓存使用率和磁盘活动相比,担心算术运算毫无意义。它在任何真正的生产程序中都不会有什么不同。
为了防止误解,我并不是说差异很小,所以不用担心,我是说它是零。在编写一个简单的程序时,ALU时间的差异与CPU等待内存或I/O的时间不完全重叠。
- #定义c-预处理器常量..我做错了什么
- 用C++中的一个变量定义一个常量
- 什么时候在C++中返回常量引用是个好主意
- 代理对象的常量正确性
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 通过多个头文件使用常量变量
- 在cuda线程之间共享大量常量数据
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 是默认情况下分配给char数组常量的值
- 私有类型的静态常量成员
- 类似枚举的计算常量
- 递归模板化函数不能分配给具有常量限定类型"const tt &"的变量"state"
- 为什么我可以通过引用修改常量返回
- 如何创建长度由常量参数指定的数组
- 当一个值是非常量但用常量表达式初始化时使用constexpr
- 返回常量对象引用 (getter) 和仅返回字符串有什么区别?
- 隐式常量/非常量运算符布尔
- 非常量变量只读位置的赋值
- 用于高性能加法和乘法的常量形式