【问题标题】:Does typecasting consume extra CPU cycles类型转换是否会消耗额外的 CPU 周期
【发布时间】:2013-05-08 12:16:49
【问题描述】:

C/C++ 中的类型转换会导致额外的 CPU 周期吗?

我的理解是,至少在某些情况下应该消耗额外的 CPU 周期。就像从浮点类型转换为整数一样,CPU 需要将浮点结构转换为整数。

float a=2.0;
int b= (float)a;

我想了解它会/不会消耗额外 CPU 周期的情况。

【问题讨论】:

  • 这取决于演员表的类型。
  • 既然你已经标记了这个 C++,我建议使用 C++ 风格的演员表,即static_cast<T>dynamic_cast<T>reinterpret_cast<T>const_cast<T>
  • 即使排除所有与语言相关的细节和细节,演员表是否“免费”也可能高度依赖于平台。例如,在 x86 上,从较大类型到较小类型的大多数整数转换都是免费的,但在具有不同特征的其他平台上可能并非如此。
  • +1 好问题,@JoeGauterin (size_t)int(通常用于循环时的 malloc)会消耗额外的 cicles 吗?
  • @DavidRF:取决于您的本地平台上如何定义size_tint,以及编译器上的优化器有多好。如有疑问,请检查组件。

标签: c++ c performance


【解决方案1】:

我想说的是“类型之间的转换”是我们应该关注的,而不是是否有强制转换。例如

 int a = 10;
 float b = a; 

将等同于:

 int a = 10;
 float b = (float)a;

这也适用于更改类型的大小,例如

 char c = 'a';
 int b = c; 

这将“将 c 从单个字节扩展为 int 大小 [使用 C 意义上的字节,而不是 8 位意义上的]”,这可能会添加额外的指令(或额外的时钟周期)到所使用的指令)超出数据移动本身。

请注意,有时这些转换并不明显。在 x86-64 上,一个典型的例子是使用 int 而不是 unsigned int 作为数组中的索引。由于指针是 64 位的,因此需要将索引转换为 64 位。在无符号的情况下,这很简单——只需使用值已经存在的寄存器的 64 位版本,因为 32 位加载操作会将寄存器的顶部填充为零。但如果你有一个int,它可能是负面的。因此编译器将不得不使用“符号将此扩展到 64 位”指令。这通常不是基于固定循环计算索引并且所有值都是正数的问题,但是如果您调用一个不清楚参数是正数还是负数的函数,编译器肯定必须扩展该值.同样,如果一个函数返回一个用作索引的值。

但是,任何相当称职的编译器都不会盲目地添加指令以将某些内容从其自己的类型转换为自身(如果关闭优化,它可能会这样做 - 但最小优化应该看到“我们正在从类型 X 转换为类型 X,那不代表什么,让我们把它拿走”)。

因此,简而言之,上面的示例并没有增加任何额外的惩罚,但肯定存在“将数据从一种类型转换为另一种类型确实会为代码添加额外的指令和/或时钟周期”的情况。

【讨论】:

  • 更改大小并不总是很重要,因为在通用寄存器中,寄存器的某些部分有别名......当然可以......但即使在堆栈上,因为您通常可以获取更大版本的无符号值。
  • 将值变大有两个问题:1.“上部”是什么,2.上部应该是什么。无符号值,在 x86-64 上,按设计工作,因为寄存器的上部用零填充。但是如果寄存器应该是符号扩展的,它需要填充任何符号,这意味着需要额外的指令。当然,从堆栈中读取 64 位可能行不通,因为绝对不能保证高 32 位实际上是您想要的值。
【解决方案2】:

它会消耗周期来改变底层表示。因此,如果您将 float 转换为 int 或反之亦然,它将消耗周期。取决于架构转换,例如 intcharlong longint 可能会或可能不会消耗周期(但通常它们会)。如果涉及多重继承,则指针类型之间的转换只会消耗周期。

【讨论】:

  • 只是一个小评论。 long long to int 不应仅在 sizeof(long long)==sizeof(int) 的情况下消耗周期。
  • @Vishal 这实际上不是真的,因为某些架构允许您在使用时将寄存器重新解释为 32 位或 64 位。 ARM 就是这样一个例子。演员阵容通常不需要单独进行。
【解决方案3】:

有不同类型的演员表。对于不同类型的强制转换,C++ 有不同类型的强制转换运算符。如果我们用这些术语来看待它,...

static_cast 在从一种类型转换为另一种类型时通常会产生成本,尤其是在目标类型与源类型的大小不同时。 static_casts 有时用于将指针从派生类型转换为基类型。这也可能会产生成本,尤其是在派生类有多个基类的情况下。

reinterpret_cast 通常不会有直接成本。粗略地说,这种类型的转换不会改变值,它只会改变它的解释方式。但是请注意,这可能会产生间接成本。如果您将指向字节数组的指针重新解释为指向 int 的指针,那么每次取消引用该指针时都可能会付出代价,除非指针按照平台的预期对齐。

const_cast 如果您要添加或删除 constness,则不应花费任何成本,因为它主要是编译器的注释。如果您使用它来添加或删除 volatile 限定符,那么我想可能会有性能差异,因为它会启用或禁用某些优化。

dynamic_cast,用于从指向基类的指针转换为指向派生类的指针,肯定有成本,因为它必须 - 至少 - 检查转换是否合适。

当您使用传统的 C 转换时,您实际上只是要求编译器选择更具体的转换类型。所以要弄清楚你的 C 转换是否有成本,你需要弄清楚它到底是什么类型的转换。

【讨论】:

  • 是的,对于 C 代码,它确实需要 2 个问题,这通常是汇编级别和 C++ 代码的区别,最终可能会执行初始化程序和模板代码,并且可能是 1000 次操作看起来很简单的演员表,从程序员的角度来看是自动的。
【解决方案4】:

DL 并享受 Agner Fog 的手册:
http://www.agner.org/optimize/
1. 用 C++ 优化软件:Windows、Linux 和 Mac 平台的优化指南
这是一个巨大的 PDF,但首先你可以查看:

14.7 不要混合浮点数和双精度
14.8 浮点数与整数的转换

【讨论】: