这似乎是一个愚蠢的问题,但是看到Alexandre C在另一个主题中的答复,我很好奇知道内置类型是否存在性能差异:
char
VSshort
VSint
主场迎战float
主场迎战double
。
通常我们在现实生活项目中不会考虑这种性能差异(如果有的话),但是出于教育目的,我想知道这一点。可以问的一般问题是:
积分算术和浮点算术之间是否有性能差异?
哪个更快?更快的原因是什么?请解释一下。
这似乎是一个愚蠢的问题,但是看到Alexandre C在另一个主题中的答复,我很好奇知道内置类型是否存在性能差异:
char
VSshort
VSint
主场迎战float
主场迎战double
。
通常我们在现实生活项目中不会考虑这种性能差异(如果有的话),但是出于教育目的,我想知道这一点。可以问的一般问题是:
积分算术和浮点算术之间是否有性能差异?
哪个更快?更快的原因是什么?请解释一下。
Answers:
浮点数与整数:
从历史上看,浮点运算可能比整数运算要慢得多。在现代计算机上,情况已不再是如此(在某些平台上,速度稍慢一些,但是除非您编写完美的代码并针对每个周期进行优化,否则差异将被代码中的其他效率低下所淹没)。
在某些有限的处理器上(例如高端手机中的处理器),浮点数可能会比整数慢一些,但只要有可用的硬件浮点数,浮点数通常在一个数量级(或更好)之内。值得注意的是,随着手机被要求运行越来越多的通用计算工作负载,这种差距正在迅速缩小。
在非常有限的处理器(便宜的手机和烤面包机)上,通常没有浮点硬件,因此浮点操作需要在软件中进行仿真。这很慢-比整数算术要慢几个数量级。
就像我说过的那样,人们期望他们的电话和其他设备的行为越来越像“真实的计算机”,并且硬件设计人员正在迅速增强FPU以满足这种需求。除非您要追踪每个上一个周期,否则除非您为很少或没有浮点支持的非常有限的CPU编写代码,否则性能差异对您而言并不重要。
不同大小的整数类型:
通常,CPU最快可以以其本机字大小的整数进行操作(有些警告是关于64位系统的)。32位操作通常比现代CPU上的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),因此性能是相同的。在其他情况下,浮点数和双精度型都具有适当的硬件支持,但是由于浮点型具有较少的位,因此它可以更快地完成,相对于双精度运算,通常可以将延迟减少一点。
免责声明:所有提到的时序和特性都只是从内存中提取的。我没有看任何东西,所以可能是错误的。;)
对于不同的整数类型,答案取决于CPU体系结构。x86架构由于其悠久的历史而不得不原生支持8、16、32(今天是64)位操作,并且总体而言,它们都同样快(它们使用基本相同的硬件,而零取出所需的高位)。
但是,在其他CPU上,小于或的数据类型int
加载/存储的成本可能更高(将字节写入内存可能必须通过加载其所在的整个32位字,然后进行位屏蔽来更新)来完成。寄存器中的单个字节,然后将整个字写回)。同样,对于大于的数据类型int
,某些CPU可能必须将操作分为两部分,分别分别加载/存储/计算下半部分和上半部分。
但是在x86上,答案是它几乎没有关系。由于历史原因,要求CPU对每种数据类型都具有相当强大的支持。因此,您可能会注意到的唯一区别是浮点运算具有更长的延迟(但吞吐量相似,因此,至少在您正确编写代码的情况下,它们本身并不慢)。
我认为没有人提到整数提升规则。在标准C / C ++中,不能对小于的类型执行任何操作int
。如果char或short在当前平台上碰巧小于int,则它们将隐式提升为int(这是错误的主要来源)。编译器需要执行此隐式升级,没有违反标准就无法解决。
整数提升意味着与int相比,在较小的整数类型上不会发生该语言中的任何操作(加法,按位,逻辑等)。因此,对char / short / int的操作通常相当快,因为前者的操作被提升为后者。
在整数提升之外,还有“通常的算术转换”,这意味着C努力使两个操作数具有相同的类型,如果两个操作数不同,则将它们之一转换为较大的两个。
但是,CPU可以在8、16、32等级别上执行各种加载/存储操作。在8位和16位体系结构上,这通常意味着尽管进行了整数提升,但8位和16位类型却更快。在32位CPU上,实际上可能意味着较小的类型速度较慢,因为它希望将所有内容整齐地排列在32位块中。32位编译器通常会优化速度,并在比指定的更大的空间中分配较小的整数类型。
尽管通常较小的整数类型当然要比较大的整数类型占用更少的空间,所以如果要优化RAM大小,则首选它们。
uint8_t b=255;
,然后执行,uint8_t a = (b + 1)/256;
则结果必须为1而不是0。如果有,uint8_t b; b << 15
则编译器必须在int
16位的情况下调用未定义的行为。等等。
上面的第一个答案很好,我将其中的一小部分复制到了以下重复的副本中(因为这是我最先结束的地方)。
“ 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
将5添加到1100100 32位整数:276us
初始化并设置1100100 64位整数:2201us
将5添加到1100100 64位整数:659us
积分算术和浮点算术之间是否有性能差异?
是。但是,这很大程度上取决于平台和CPU。不同的平台可以以不同的速度执行不同的算术运算。
话虽如此,有关的答复更为具体。 pow()
是适用于双精度值的通用例程。通过为它提供整数值,它仍然可以完成处理非整数指数所需的所有工作。使用直接乘法会绕开很多复杂性,而这正是速度发挥作用的地方。实际上,这实际上不是不同类型的问题,而是要绕过使用任何指数使pow函数所需的大量复杂代码。
double
数学运算可能会很慢,因为它无法存储在单个寄存器中。但是,32位整数可能会非常快。数字和类型产生了巨大的差异,但是还有许多其他问题……您会在嵌入式系统工作中看到更多的东西,顺便说一句,因为与通用桌面计算相比,在这里它往往非常受限制……
通常,整数数学比浮点数学快。这是因为整数数学涉及更简单的计算。但是,在大多数操作中,我们谈论的时钟少于十二个。不是毫,微米,纳米或壁虱;时钟。在现代内核中,每秒发生2-3亿次之间的事件。另外,由于486的许多内核都具有一组浮点处理单元(FPU),这些单元硬连线以有效地执行浮点运算,并且通常与CPU并行。
结果,尽管从技术上讲它比较慢,但浮点计算仍然如此之快,以至于任何试图对差值进行计时的尝试都会在计时机制和线程调度中固有比执行计算所需的更多错误。在可能的情况下使用int,但在不能的情况下使用int,不必过多担心相对计算速度。