【问题标题】:How to write int64=int32*int32 in a standard/portable and efficient way? [closed]如何以标准/便携和高效的方式编写 int64=int32*int32? [关闭]
【发布时间】:2015-04-11 14:40:53
【问题描述】:

相关: Is this treatment of int64_t a GCC AND Clang bug?

我能想到的唯一解决方案是将其中一个操作数显式转换为int64,强制乘积也至少为int64

但是如果这样做,那么编译器的智能就可以实际执行int64*int32,或int64*int64,或者理想情况下,将其优化回int32*int32

正如相关问题中所讨论的,将int32*int32 的结果分配给int64 并不会改变int32*int32 已经导致UB 的事实。

有什么想法吗?

【问题讨论】:

  • C 和 C++ 是不同的语言,但您已经用这两种语言标记了您的问题。如果您实际上是在尝试提出两个单独的问题(“我如何在 C 中可移植地执行此操作?”和“我如何在 C++ 中可移植地执行此操作?”),那么您应该分别发布它们。如果您特别需要一个可移植且在两者中定义明确的解决方案,请明确说明(并可能提及原因,因为这不是常见要求)。
  • 我看不出你标记为相关的问题实际上与这个问题有什么关系。
  • 如果你的编译器不能为这样一个基本的东西生成最佳代码,你可能应该寻找另一个编译器来编写一些性能关键的东西。

标签: c++ c


【解决方案1】:

您已经说明了如何以标准、便携和高效的方式执行此操作:

int64_t mul(int32_t x, int32_t y) {
    return (int64_t)x * y;
    // or static_cast<int64_t>(x) * y if you prefer not to use C-style casts
    // or static_cast<int64_t>(x) * static_cast<int64_t>(y) if you don't want
    // the integral promotion to remain implicit
}

您的问题似乎是关于具有与函数签名相对应的汇编指令的假设架构

int64_t intrinsic_mul(int32_t x, int32_t y);
int64_t intrinsic_mul(int64_t x, int64_t y);
int64_t intrinsic_mul(int64_t x, int32_t y); // and maybe this too

并且,在这个假设的架构上,第一个具有相关优势,此外,您的编译器在编译上述函数时无法使用该指令,最重要的是,它会失败提供对上述内在函数的访问。

我希望这种情况很少见,但如果你真的发现自己处于这种情况,大多数编译器还允许你编写内联汇编,所以你可以编写一个调用它的函数直接特殊指令,并且仍然提供足够的元数据,以便优化器可以稍微有效地使用它(例如,使用符号输入和输出寄存器,以便优化器可以使用它想要的任何寄存器,而不是硬编码寄存器选择)。

【讨论】:

  • 没有充分的理由强制转换返回值。你的编译器知道返回什么类型
  • @mfro:但是,我必须转换参数之一,否则产品将是 int32_t,因此在转换为 int64_t 之前会失去精度并容易溢出。 (或者,我想,如果我有心情,我可以直接提出 int64_t 的论点)
  • 但是如果你直接使用int64_t这个参数,那么编译器就不能自动选择第一个版本的内在集。目前的写法是最好的。
【解决方案2】:

内置算术表达式仅对同类操作数类型存在。任何涉及混合类型的表达式都意味着整数提升,并且算术运算本身只为同类类型定义和应用。

选择int32_tint64_t

正如您可能正确理解的那样,对于两种类型的算术运算(至少+-*)都容易受到溢出的 UB 影响,但是在两个 @987654327 上进行运算时不会出现溢出@s 两者都可以表示为int32_ts。因此,例如以下工作:

int64_t multiply(int32_t a, int32_t b)
{
    // guaranteed not to overflow, and the result value is equal
    // to the mathematical result of the operation
    return static_cast<int64_t>(a) * static_cast<int64_t>(b);
}

例如,下面是 GCC 如何在 Linux 上将其转换为 x86 和 x86_64(注意不同的调用约定):

multiply(int, int):

// x86 (32-bit, "-m32 -march=i386")     x86-64 ("-m64 -march=x86-64")
// args are on the stack                args are in EDI, ESI
// return in EDX:EAX                    return in RAX

mov     eax, DWORD PTR [esp+8]          movsx   rax, edi
                                        movsx   rsi, esi
imul    DWORD PTR [esp+4]               imul    rax, rsi
ret                                     ret

【讨论】:

  • OP 意识到了这一点,但似乎确信编译器会生成一些免费的 64x64 乘法结构来实现它。
  • @R..:为什么是“无偿”?有什么选择?
  • 通过范围分析(或者在这种情况下更可能是窥孔优化),编译器应该知道操作数都是 32 位的,并且 32x32 乘法运算就足够了。
  • @R..:你觉得GCC's generated code 不是最理想的吗?
  • 不,AFAIK GCC 做的完全正确。但 OP 对此表示怀疑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-18
  • 1970-01-01
  • 1970-01-01
  • 2018-11-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多