繁体   English   中英

内置类型的性能:char vs short vs int vs. float vs. double

[英]Performance of built-in types : char vs short vs int vs. float vs. double

这可能看起来有点愚蠢但是看到Alexandre C在其他主题中的回复 ,我很想知道如果内置类型有任何性能差异:

char vs short vs int vs. float vs. double

通常我们在现实生活中没有考虑这种性能差异(如果有的话),但我想知道这是出于教育目的。 可以问的一般问题是:

  • 整数算术和浮点运算之间是否有任何性能差异?

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

浮点数与整数:

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

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

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

正如我所说,人们期望他们的手机和其他设备的行为越来越像“真正的计算机”,硬件设计师正在迅速加强FPU以满足这种需求。 除非您追逐每个循环,或者您正在为极少或没有浮点支持的非常有限的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会吝啬这些指令。

几乎所有处理器的低需求,高复杂度操作通常都会很慢; 没有足够的好处来证明成本合理。

如果有人不愿意考虑它们,那么低需求,低复杂度的操作会很快,否则就不存在。

进一步阅读:

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

绝对。

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

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

  • 对于简单的操作,积分类型很快 例如,整数加法通常只有一个周期的延迟,整数乘法通常约为2-4个周期,即IIRC。
  • 浮点类型用于执行速度慢得多。 然而,在今天的CPU上,它们具有出色的吞吐量,并且每个浮点单元通常可以在每个周期中退出操作,从而导致与整数运算相同(或类似)的吞吐量。 但是,延迟通常更糟。 浮点加法通常具有大约4个周期的延迟(对于整数而言为1)。
  • 对于一些复杂的操作,情况是不同的,甚至是逆转的。 例如,对FP的划分可能具有比整数更少的延迟,这仅仅是因为在两种情况下操作都很复杂,但它在FP值上更常用,因此可以花费更多精力(和晶体管)来优化该情况。

在某些CPU上,双精度可能比浮点数慢得多。 在某些体系结构中,没有用于双精度的专用硬件,因此它们通过传递两个浮点大小的块来处理,从而使您的吞吐量更低,延迟时间延长两倍。 在其他(例如x86 FPU)上,两种类型都转换为相同的内部格式80位浮点(在x86的情况下),因此性能相同。 在其他情况下,float和double都有适当的硬件支持,但由于float具有较少的位,因此可以更快地完成,通常相对于双操作减少一点延迟。

免责声明:所有提及的时间和特征都是从内存中提取的。 我看起来没什么,所以可能是错的。 ;)

对于不同的整数类型,答案根据CPU架构而有很大差异。 x86架构由于其悠久的历史,必须本身支持8,16,32(以及今天的64)位操作,并且通常它们都同样快速(它们使用基本相同的硬件,并且只有零根据需要输出高位。

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

但是在x86上,答案是它并不重要。 由于历史原因,CPU需要对每种数据类型都提供非常强大的支持。 因此,您可能注意到的唯一区别是浮点运算具有更多延迟(但吞吐量相似,因此它们本身并不 ,至少如果您正确编写代码)

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

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

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

但是,CPU可以在8,16,32等级执行各种加载/存储操作。 在8位和16位架构上,这通常意味着尽管有整数提升,但8位和16位类型更快。 在32位CPU上,它实际上可能意味着较小的类型较慢 ,因为它希望将所有内容整齐地排列在32位块中。 32位编译器通常优化速度,并在比指定空间更大的空间中分配更小的整数类型。

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

上面的第一个答案很棒,我把它的一小部分复制到下面的副本中(因为这是我最先结束的地方)。

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

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

#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结果:

Initialise&Set 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()是一个通用的例程,适用于double值。 通过提供整数值,它仍然可以完成处理非整数指数所需的所有工作。 使用直接乘法会绕过很多复杂性,这就是速度发挥作用的地方。 这实际上不是一个(不是很多)不同类型的问题,而是绕过了使用任何指数制作pow函数所需的大量复杂代码。

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

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

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

如果浮点计算由软件模拟,那么积分算法将更快。

如有疑问,请查看。

在优化之前使编程正常且稳健。

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

浮点和整数运算之间肯定存在差异。 根据CPU的特定硬件和微指令,您可以获得不同的性能和/或精度。 精确描述的好谷歌术语(我也不确切):

FPU x87 MMX SSE

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

通常,整数数学比浮点数学更快。 这是因为整数数学涉及更简单的计算。 但是,在大多数操作中,我们谈论的是不到十几个时钟。 不是毫克,微米,纳米或蜱; 时钟。 在现代核心中每秒发生2-3亿次的情况。 此外,由于486许多内核具有一组浮点处理单元或FPU,它们通过硬连线有效地执行浮点运算,并且通常与CPU并行。

由于这些原因,虽然技术上它的速度较慢,但​​浮点计算仍然如此之快,以至于任何计时差异的尝试都会在计时机制和线程调度中产生比实际执行计算所需的更多错误。 尽可能使用int,但在不能的时候理解,并且不要过分担心相对计算速度。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM