这似乎是一个有点愚蠢的问题,但看到 Alexandre C 在另一个主题中的回复,我很想知道内置类型是否有任何性能差异:
char
对short
对int
对float
对double
。
通常我们在现实生活中的项目中不会考虑这种性能差异(如果有的话),但出于教育目的,我想知道这一点。可以问的一般问题是:
积分算术和浮点算术之间有性能差异吗?
哪个更快?速度更快的原因是什么?请解释一下。
这似乎是一个有点愚蠢的问题,但看到 Alexandre C 在另一个主题中的回复,我很想知道内置类型是否有任何性能差异:
char
对short
对int
对float
对double
。
通常我们在现实生活中的项目中不会考虑这种性能差异(如果有的话),但出于教育目的,我想知道这一点。可以问的一般问题是:
积分算术和浮点算术之间有性能差异吗?
哪个更快?速度更快的原因是什么?请解释一下。
浮点数与整数:
从历史上看,浮点数可能比整数运算慢得多。在现代计算机上,情况已不再如此(在某些平台上速度稍慢,但除非您编写完美的代码并针对每个周期进行优化,否则代码中的其他低效率将淹没差异)。
在一些有限的处理器上,比如高端手机中的处理器,浮点可能比整数慢一些,但它通常在一个数量级(或更好)之内,只要有硬件浮点可用。值得注意的是,随着手机被要求运行越来越多的通用计算工作负载,这一差距正在迅速缩小。
在非常有限的处理器(廉价手机和烤面包机)上,通常没有浮点硬件,因此需要在软件中模拟浮点运算。这很慢——比整数算术慢几个数量级。
正如我所说,人们期望他们的手机和其他设备的行为越来越像“真正的计算机”,而硬件设计师正在迅速加强 FPU 以满足这种需求。除非您正在追逐每个最后一个周期,或者您正在为非常有限的 CPU 编写代码,这些 CPU 很少或没有浮点支持,否则性能差异对您来说并不重要。
不同大小的整数类型:
通常,CPU在处理其原生字长的整数时速度最快(有一些关于 64 位系统的警告)。在现代 CPU 上,32 位操作通常比 8 位或 16 位操作更快,但这在不同架构之间存在很大差异。另外,请记住,您不能孤立地考虑 CPU 的速度。它是复杂系统的一部分。即使对 16 位数字进行操作比对 32 位数字进行操作慢 2 倍,当您使用 16 位数字而不是 32 位表示数据时,您可以将两倍的数据放入缓存层次结构中。如果这使得所有数据都来自缓存而不是频繁缓存未命中之间存在差异,那么更快的内存访问将胜过 CPU 的较慢运行。
其他注意事项:
向量化进一步平衡了更窄的类型(float
以及 8 位和 16 位整数)——您可以在相同宽度的向量中执行更多操作。然而,好的矢量代码很难编写,所以如果没有大量的仔细工作,你就不会获得这种好处。
为什么会有性能差异?
实际上只有两个因素会影响 CPU 上的操作是否快速:操作的电路复杂性,以及用户对操作快速的需求。
(在合理范围内)如果芯片设计者愿意在问题上投入足够多的晶体管,任何操作都可以快速完成。但是晶体管要花钱(或者更确切地说,使用大量晶体管会使您的芯片更大,这意味着您每个晶圆获得的芯片更少,产量更低,这需要花钱),因此芯片设计人员必须平衡用于哪些操作的复杂性,以及他们根据(感知的)用户需求来做这件事。粗略地说,您可能会考虑将操作分为四类:
high demand low demand
high complexity FP add, multiply division
low complexity integer add popcount, hcf
boolean ops, shifts
几乎所有 CPU 上的高需求、低复杂性操作都将很快:它们是唾手可得的果实,并为每个晶体管提供最大的用户利益。
高需求、高复杂性的操作在昂贵的 CPU(如计算机中使用的 CPU)上会很快,因为用户愿意为它们付费。但是,您可能不愿意为您的烤面包机支付额外的 3 美元来获得快速的 FP 乘法,因此廉价的 CPU 会忽略这些指令。
几乎所有处理器上的低需求、高复杂性操作通常都会很慢;只是没有足够的好处来证明成本是合理的。
低需求、低复杂性的操作如果有人费心去想它们就会很快,否则就不存在了。
进一步阅读:
绝对地。
首先,当然,它完全取决于所讨论的 CPU 架构。
但是,整数和浮点类型的处理方式非常不同,因此几乎总是这样:
在某些 CPU 上,双精度数可能比浮点数慢得多。在某些架构上,没有用于双精度的专用硬件,因此它们是通过传递两个浮点大小的块来处理的,这会给您带来更差的吞吐量和两倍的延迟。在其他(例如 x86 FPU)上,两种类型都转换为相同的内部格式 80 位浮点,在 x86 的情况下),因此性能是相同的。在另一些情况下,float 和 double 都具有适当的硬件支持,但由于 float 具有较少的位,因此可以更快地完成,通常相对于 double 操作减少一点延迟。
免责声明:所有提到的时间和特征都是从内存中提取的。我没有仔细看,所以可能是错的。;)
对于不同的整数类型,答案因 CPU 架构而异。x86 架构,由于其漫长的复杂历史,必须原生支持 8、16、32(和今天的 64)位操作,而且一般来说,它们都同样快(它们使用基本相同的硬件,而且只是零根据需要取出高位)。
但是,在其他 CPU 上,小于 an 的数据类型的int
加载/存储成本可能更高(将字节写入内存可能必须通过加载它所在的整个 32 位字来完成,然后进行位掩码以更新寄存器中的单个字节,然后将整个字写回)。同样,对于大于 的数据类型int
,某些 CPU 可能必须将操作分成两部分,分别加载/存储/计算下半部分和上半部分。
但在 x86 上,答案是它几乎无关紧要。由于历史原因,CPU 需要对每种数据类型都有相当强大的支持。因此,您可能会注意到的唯一区别是浮点运算具有更多延迟(但吞吐量相似,因此它们本身并不慢,至少在您正确编写代码的情况下)
I don't think anyone mentioned the integer promotion rules. In standard C/C++, no operation can be performed on a type smaller than int
. If char or short happen to be smaller than int on the current platform, they are implicitly promoted to int (which is a major source of bugs). The complier is required to do this implicit promotion, there's no way around it without violating the standard.
The integer promotions mean that no operation (addition, bitwise, logical etc etc) in the language can occur on a smaller integer type than int. Thus, operations on char/short/int are generally equally fast, as the former ones are promoted to the latter.
And on top of the integer promotions, there's the "usual arithmetic conversions", meaning that C strives to make both operands the same type, converting one of them to the larger of the two, should they be different.
However, the CPU can perform various load/store operations on 8, 16, 32 etc level. On 8- and 16 bit architectures, this often means that 8 and 16 bit types are faster despite the integer promotions. On a 32 bit CPU it might actually mean that the smaller types are slower, because it wants to have everything neatly aligned in 32-bit chunks. 32 bit compilers typically optimize for speed and allocate smaller integer types in larger space than specified.
Though generally the smaller integer types of course take less space than the larger ones, so if you intend to optimize for RAM size, they are to prefer.
上面的第一个答案很棒,我将其中的一小部分复制到了下一个副本中(因为这是我首先结束的地方)。
我想提供以下代码,这些代码描述了各种整数大小的分配、初始化和一些算术:
#include <iostream>
#include <windows.h>
using std::cout; using std::cin; using std::endl;
LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
LARGE_INTEGER Frequency;
void inline showElapsed(const char activity [])
{
QueryPerformanceCounter(&EndingTime);
ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
ElapsedMicroseconds.QuadPart *= 1000000;
ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
cout << activity << " took: " << ElapsedMicroseconds.QuadPart << "us" << endl;
}
int main()
{
cout << "Hallo!" << endl << endl;
QueryPerformanceFrequency(&Frequency);
const int32_t count = 1100100;
char activity[200];
//-----------------------------------------------------------------------------------------//
sprintf_s(activity, "Initialise & Set %d 8 bit integers", count);
QueryPerformanceCounter(&StartingTime);
int8_t *data8 = new int8_t[count];
for (int i = 0; i < count; i++)
{
data8[i] = i;
}
showElapsed(activity);
sprintf_s(activity, "Add 5 to %d 8 bit integers", count);
QueryPerformanceCounter(&StartingTime);
for (int i = 0; i < count; i++)
{
data8[i] = i + 5;
}
showElapsed(activity);
cout << endl;
//-----------------------------------------------------------------------------------------//
//-----------------------------------------------------------------------------------------//
sprintf_s(activity, "Initialise & Set %d 16 bit integers", count);
QueryPerformanceCounter(&StartingTime);
int16_t *data16 = new int16_t[count];
for (int i = 0; i < count; i++)
{
data16[i] = i;
}
showElapsed(activity);
sprintf_s(activity, "Add 5 to %d 16 bit integers", count);
QueryPerformanceCounter(&StartingTime);
for (int i = 0; i < count; i++)
{
data16[i] = i + 5;
}
showElapsed(activity);
cout << endl;
//-----------------------------------------------------------------------------------------//
//-----------------------------------------------------------------------------------------//
sprintf_s(activity, "Initialise & Set %d 32 bit integers", count);
QueryPerformanceCounter(&StartingTime);
int32_t *data32 = new int32_t[count];
for (int i = 0; i < count; i++)
{
data32[i] = i;
}
showElapsed(activity);
sprintf_s(activity, "Add 5 to %d 32 bit integers", count);
QueryPerformanceCounter(&StartingTime);
for (int i = 0; i < count; i++)
{
data32[i] = i + 5;
}
showElapsed(activity);
cout << endl;
//-----------------------------------------------------------------------------------------//
//-----------------------------------------------------------------------------------------//
sprintf_s(activity, "Initialise & Set %d 64 bit integers", count);
QueryPerformanceCounter(&StartingTime);
int64_t *data64 = new int64_t[count];
for (int i = 0; i < count; i++)
{
data64[i] = i;
}
showElapsed(activity);
sprintf_s(activity, "Add 5 to %d 64 bit integers", count);
QueryPerformanceCounter(&StartingTime);
for (int i = 0; i < count; i++)
{
data64[i] = i + 5;
}
showElapsed(activity);
cout << endl;
//-----------------------------------------------------------------------------------------//
getchar();
}
/*
My results on i7 4790k:
Initialise & Set 1100100 8 bit integers took: 444us
Add 5 to 1100100 8 bit integers took: 358us
Initialise & Set 1100100 16 bit integers took: 666us
Add 5 to 1100100 16 bit integers took: 359us
Initialise & Set 1100100 32 bit integers took: 870us
Add 5 to 1100100 32 bit integers took: 276us
Initialise & Set 1100100 64 bit integers took: 2201us
Add 5 to 1100100 64 bit integers took: 659us
*/
我在 i7 4790k 上的 MSVC 中的结果:
初始化和设置 1100100 8 位整数占用:444us
将 5 添加到 1100100 8 位整数占用:358us
初始化和设置 1100100 16 位整数占用:666us
将 5 添加到 1100100 16 位整数占用:359us
初始化和设置 1100100 32 位整数占用:870us
将 5 添加到 1100100 32 位整数占用:276us
初始化和设置 1100100 64 位整数占用:2201us
将 5 添加到 1100100 64 位整数占用:659us
积分算术和浮点算术之间有性能差异吗?
是的。但是,这是非常特定于平台和 CPU 的。不同的平台可以以不同的速度进行不同的算术运算。
话虽如此,有问题的答复更加具体。 pow()
是一个适用于双精度值的通用例程。通过给它提供整数值,它仍然可以完成处理非整数指数所需的所有工作。使用直接乘法绕过了很多复杂性,这就是速度发挥作用的地方。这实际上不是不同类型的问题(太多),而是绕过使用任何指数生成 pow 函数所需的大量复杂代码。
通常,整数数学比浮点数学快。这是因为整数数学涉及更简单的计算。然而,在大多数操作中,我们谈论的时钟少于十几个。不是毫秒、微米、纳米或滴答;时钟。在现代核心中每秒发生 2-30 亿次的情况。此外,由于 486 很多内核都有一组浮点处理单元或 FPU,这些单元通过硬接线可以有效地执行浮点运算,并且通常与 CPU 并行。
因此,尽管从技术上讲它较慢,但浮点计算仍然如此之快,以至于任何对时间差进行计时的尝试都会在计时机制和线程调度中产生比实际执行计算所需的更多错误。能用的时候用int,不能用的时候要明白,不用太担心相对计算速度。
取决于处理器和平台的组成。
具有浮点协处理器的平台可能比积分运算要慢,因为必须将值传入和传出协处理器。
如果浮点处理在处理器的核心内,则执行时间可以忽略不计。
如果用软件模拟浮点计算,那么积分运算会更快。
如有疑问,请提供个人资料。
在优化之前让编程正确且健壮地工作。
不,不是。这当然取决于 CPU 和编译器,但性能差异通常可以忽略不计——如果有的话。
浮点运算和整数运算之间肯定存在差异。根据 CPU 的特定硬件和微指令,您可以获得不同的性能和/或精度。准确描述的良好谷歌术语(我也不确切知道):
FPU x87 MMX SSE
关于整数的大小,最好使用平台/架构字长(或两倍),这归结为int32_t
x86 和int64_t
x86_64 上的。某些处理器可能具有同时处理多个这些值的内在指令(如 SSE(浮点)和 MMX),这将加速并行加法或乘法运算。