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

新手上路,请多包涵

看到Alexandre C在另一个主题的 回复,我很想知道内置类型是否有任何性能差异:

char vs short vs int vs. float vs. double .

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

  • 积分算术和浮点算术之间有性能差异吗?

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

原文由 Nawaz 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 699
2 个回答

浮点数与整数:

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

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

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

正如我所说,人们期望他们的手机和其他设备的行为越来越像“真正的计算机”,而硬件设计师正在迅速加强 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 会忽略这些指令。

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

低需求、低复杂性的操作如果有人费心去想它们就会很快,否则就不存在了。

进一步阅读:

  • Agner Fog 维护了一个不错的 网站,其中有很多关于低级性能细节的讨论(并且有非常科学的数据收集方法来支持它)。
  • 英特尔® 64 和 IA-32 架构优化参考手册(PDF 下载链接位于页面下方)也涵盖了许多此类问题,尽管它侧重于一个特定的架构系列。

原文由 Stephen Canon 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

“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 中的结果:

初始化和设置 1100100 8 位整数占用:444us

将 5 加到 1100100 8 位整数中占用:358us

初始化并设置 1100100 16 位整数占用:666us

将 5 加到 1100100 16 位整数中:359us

初始化和设置 1100100 32 位整数占用:870us

1100100 32 位整数加 5 耗时:276us

初始化并设置 1100100 64 位整数占用:2201us

将 5 加到 1100100 64 位整数中:659us

原文由 Researcher 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题