性能的内置types:字符与短与整数与浮点数与双

这似乎有点愚蠢的问题,但看到亚历山大C在另一个话题的答复 ,我很想知道,如果有什么性能与内置types的差异:

char vs short vs int vs float vs. double

通常我们在现实生活中不考虑这样的performance差异(如果有的话),但是我想知道这是为了教育目的。 一般问题可以问的是:

  • 整数运算和浮点运算有什么不同?

  • 哪个更快? 什么是更快的原因? 请解释一下。

浮点数与整数:

历史上,浮点运算可能比整数运算慢得多。 在现代计算机上,这种情况已经不再是这种情况了(在某些平台上,速度稍微慢一点,但是除非你编写完美的代码并在每个周期进行优化,否则这些差异将会被代码中的其他低效率所淹没)。

在有限的处理器上,比如在高端手机中,浮点运算可能比整数慢一些,但一般在一个数量级(或更好)的范围内,只要有硬件浮点可用。 值得注意的是,随着手机被要求运行越来越普遍的计算工作负载,这种差距正在迅速地结束。

非常有限的处理器(便宜的手机和烤面包机)上,通常没有浮点硬件,因此需要用软件模拟浮点操作。 这很慢 – 比整数算术慢了几个数量级。

正如我所说的,人们期望他们的手机和其他设备的行为越来越像“真正的电脑”,硬件devise者正在迅速加强FPU以满足这一需求。 除非您正在追逐最后一个循环,否则您正在编写非常有限的CPU的代码,这些代码几乎没有浮点支持,所以性能区别对您无关紧要。

不同大小的整数types:

通常情况下, CPU在以本地字大小整数运行时速度最快(有关64位系统的一些警告)。 现代CPU上的32位操作通常比8位或16位操作更快,但是这在架构之间会有很大的差别。 另外,请记住,不能孤立地考虑CPU的速度, 这是一个复杂系统的一部分。 即使在16位数字上操作比在32位数字上操作速度慢2倍,当用16位数字而不是32位数字表示时,也可以将高达两倍的数据放入高速caching层次结构中。 如果这使得所有的数据都来自caching,而不是频繁的caching未命中,那么更快的内存访问将胜过CPU的更慢的操作。

其他说明:

vector化技术进一步提高了天平的宽度( float和8位和16位整数) – 可以在相同宽度的vector中执行更多的操作。 然而,好的vector代码很难写,所以不会像没有经过精心的工作那样得到这个好处。

为什么有性能差异?

实际上,在CPU上操作是否快速只有两个因素:操作的电路复杂性和用户对操作的要求快。

(在合理的范围内)如果芯片devise者愿意抛出足够的晶体pipe来解决问题的话,任何操作都可以快速完成。 但是晶体pipe的成本很高(或者更确切地说,使用大量的晶体pipe会使芯片变大,这意味着每个晶圆上的芯片数量会减less,而且产量也会降低,这就需要花费金钱),所以芯片devise人员必须平衡使用哪种操作的复杂度,他们基于(感知的)用户需求来做到这一点。 粗略地说,你可能会想到把操作分成四类:

  high demand low demand high complexity FP add, multiply division low complexity integer add popcount, hcf boolean ops, shifts 

高要求,低复杂度的操作几乎可以在任何CPU上实现:它们是低成本的,每个晶体pipe可以带来最大的用户利益。

高需求,高复杂度的操作将会在昂贵的CPU(如计算机中使用的CPU)上快速运行,因为用户愿意为其付费。 你可能不愿意为你的烤面包机支付额外的3美元来获得快速的FP乘法,然而,如此便宜的CPU将会吝啬这些指令。

几乎所有处理器上的低需求,高复杂度的操作通常都是缓慢的; 没有足够的好处certificate成本。

低需求,低复杂度的操作会很快,如果有人不思考它们,而不存在其他情况。

进一步阅读:

  • Agner Fog维护一个很好的网站,其中有很多关于低级性能细节的讨论(并且有非常科学的数据收集方法来备份)。
  • 英特尔®64和IA-32体系结构优化参考手册 (PDF下载链接是页面的一部分)也涵盖了很多这些问题,尽pipe它专注于一个特定的体系结构。

绝对。

首先,当然,这完全取决于所讨论的CPU架构。

但是,积分和浮点types的处理方式非常不同,因此以下情况几乎总是如此:

  • 对于简单的操作,整数types是快速的 。 例如,整数加法通常只有一个周期的延迟,而整数乘法一般是2-4个周期,即IIRC。
  • 浮点types的执行速度要慢得多。 然而,在今天的CPU上,它们具有优异的吞吐量,每个浮点单元通常可以在每个周期中停止一个操作,从而导致与整数操作相同的(或类似的)吞吐量。 但是,延迟通常更糟。 浮点加法通常有4个周期的延迟(vs 1)。
  • 对于一些复杂的操作,情况是不一样的,甚至颠倒过来。 例如,在FP上的划分可能比在整数上的延迟要 ,因为在这两种情况下操作都很复杂,但是在FP值上更常用,所以可能需要花费更多的精力(和晶体pipe)来优化这种情况。

在一些CPU上,双打可能比浮​​点数慢得多。 在某些体系结构中,没有专门的双工硬件,因此通过传递两个浮点大小的块来处理它们,从而给您带来更差的吞吐量和两倍的延迟。 在其他的(例如x86 FPU)上,两种types都转换为相同的内部格式80位浮点,在x86的情况下),所以性能是相同的。 还有一些浮点和双精度浮点和双精度浮点运算都有适当的硬件支持,但是由于float浮点数较less,所以可以做得更快一些,相对于双精度浮点运算,通常会减less一点延迟。

免责声明:所有提到的时间和特点只是从记忆中拉出。 我没有看它,所以这可能是错的。 ;)

对于不同的整数types,答案根据CPU体系结构而变化很大。 x86体系结构由于其冗长的历史,必须在本地支持8,16,32(和今天的64位)操作,而且通常它们都是同样快的(它们基本上使用相同的硬件,只是零根据需要输出高位)。

但是,在其他CPU上,小于int数据types的加载/存储成本可能更高(写入字节到内存可能需要加载它所在的整个32位字,然后进行位掩码更新寄存器中的单个字节,然后写回整个单词)。 同样,对于大于int数据types,一些CPU可能不得不将操作拆分为两个,分别加载/存储/计算下半部分和上半部分。

但是在x86上,答案是大部分没有关系。 由于历史的原因,CPU需要对每种数据types都有相当强大的支持。 所以你可能会注意到的唯一区别是浮点运算有更多的延迟(但是吞吐量相似,所以它们本身不会变慢 ,至less如果你正确地编写代码的话)

我不认为任何人提到整数推广规则。 在标准C / C ++中,对小于int的types不能执行任何操作。 如果在当前平台上char或short小于int,则它们被隐式提升为int(这是bug的主要来源)。 编译器需要做这个暗示的提升,没有违反标准就没有办法。

整数提升意味着在整数types比int小的整数types上,不会出现任何操作(加法,按位,逻辑等)。 因此,char / short / int的操作通常同样快,因为前者被提升到后者。

除了整数提升之外,还有“通常的算术转换”,这意味着C努力使两个操作数都是相同的types,如果它们不同,就把它们中的一个转换成两个中较大的一个。

但是,CPU可以在8,16,32等级上执行各种加载/存储操作。 在8位和16位体系结构中,这通常意味着即使进行整数升级,8位和16位types也会更快。 在一个32位的CPU上,这可能意味着更小的types会更慢 ,因为它希望把所有的东西整齐排列在32位块中。 32位编译器通常针对速度进行优化,并在比指定更大的空间中分配较小的整数types。

虽然通常较小的整数types当然比较大的整数types占用较less的空间,所以如果你打算优化RAM大小,他们更喜欢。

整数运算和浮点运算有什么不同?

是。 但是,这是非常多的平台和CPU的具体情况。 不同的平台可以以不同的速度进行不同的算术运算

这就是说,有关的答复是更具体一些。 pow()是一个在double值上工作的通用例程。 通过给它提供整数值,它仍然在做所有处理非整数指数所需的工作。 使用直接乘法避开了很多复杂性,这就是速度起作用的地方。 这实际上不是一个不同types的问题(太多了),而是绕过了大量复杂的代码来使任何指数的pow函数成为可能。

取决于处理器和平台的组成。

具有浮点协处理器的平台可能比积分algorithm慢,因为必须将数据传送到协处理器和从协处理器传送数据。

如果浮点处理在处理器的核心内,则执行时间可以忽略不计。

如果浮点计算是由软件模拟的,那么积分algorithm会更快。

如有疑问,简介。

在优化之前使编程正常工作并且健壮。

不,不是。 这当然取决于CPU和编译器,但性能差异通常可以忽略不计 – 如果有的话。

浮点运算和整数运算当然有区别。 根据CPU的具体硬件和微指令,你会得到不同的性能和/或精度。 好的谷歌条款的准确描述(我不知道究竟是):

FPU x87 MMX SSE

关于整数的大小,最好使用平台/体系结构字大小(或者是double),这个大小可以归结为x86上的int32_t和x86_64上的int64_t 。 SOme处理器可能具有一次处理这些值的内在指令(如SSE(浮点)和MMX),这将加速并行加法或乘法。

一般来说,整数math比浮点math要快。 这是因为整数math涉及更简单的计算。 但是,在大多数操作中,我们所谈的不到十几个钟。 不是毫米,微米,纳米或蜱; 时钟。 现代内核中每秒发生的次数在2-3亿次之间。 而且,由于486内核中有很多浮点处理单元或浮点处理单元(FPU),这些浮点处理单元或浮点处理单元(FPU)是高效执行浮点运算的硬连线,并且通常与CPU并行工作。

由于这些原因,尽pipe在技术上速度较慢,但​​是浮点计算仍然非常快,以至于任何时间差异的尝试都会在计时机制和线程调度中具有比实际执行计算所固有的更多的错误。 尽可能使用整数,但不能理解,不要太担心相对计算速度。

上面的第一个答案很好,我把它的一小块复制到下面的重复(因为这是我第一次结束的地方)。

“char”和“small int”比“int”慢吗?

我想提供以下代码configuration文件分配,初始化和对各种整数大小进行一些算术运算:

 #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