c# - 否定浮点变量的不同方法生成不同的程序集
问题描述
我有以下代码:
public static float F(float n)
{
return -n;
}
生成以下asm:
Program.F(Single)
L0000: push eax
L0001: vzeroupper
L0004: vmovss xmm1, [esp+8]
L000a: vmovss xmm0, [Program.F(Single)] ; remember this line
L0012: vxorps xmm0, xmm0, xmm1 ; remember this line
L0016: vmovss [esp], xmm0
L001b: fld st, dword ptr [esp]
L001e: pop ecx
L001f: ret 4
另一方面,我有以下片段:
public static float G(float n)
{
return n * -1;
}
生成:
Program.G(Single)
L0000: push eax
L0001: vzeroupper
L0004: vmovss xmm0, [esp+8]
L000a: vmulss xmm0, xmm0, [Program.G(Single)] ; remember this line
L0012: vmovss [esp], xmm0
L0017: fld st, dword ptr [esp]
L001a: pop ecx
L001b: ret 4
问题
- 正如您可能注意到的那样,输出有点不同。为什么我们有 2 个不同的程序集?代码不执行完全相同的任务吗?
- 有这么大块组装的原因吗?如果是,为什么?
- 我怎样才能获得更有效的版本。
笔记
NOTE用于最后一个问题。
我想基本上得到像“C”这样的程序集。例如:
float
f(float n) {
return -n;
}
float
g(float n) {
return n * -1;
}
生成:
f:
xorps xmm0, XMMWORD PTR .LC0[rip]
ret
g:
xorps xmm0, XMMWORD PTR .LC0[rip]
ret
.LC0:
.long -2147483648
.long 0
.long 0
.long 0
- 我尝试了以下操作,但效果不佳:
public static unsafe float F(float n)
{
uint i = *(uint*)&n;
i ^= 0x80000000;
n = *(float*)&i;
return n;
}
我得到了以下我认为效率更低的内容:
Program.F(Single)
L0000: sub esp, 8
L0003: vzeroupper
L0006: mov eax, [esp+0xc]
L000a: mov [esp], eax
L000d: mov eax, [esp]
L0010: xor eax, 0x80000000
L0015: mov [esp], eax
L0018: vmovss xmm0, [esp]
L001d: vmovss [esp+0xc], xmm0
L0023: vmovss xmm0, [esp+0xc]
L0029: vmovss [esp+4], xmm0
L002f: fld st, dword ptr [esp+4]
L0033: add esp, 8
L0036: ret 4
- 我犯了一个愚蠢的错误(见评论)。忘记切换
x64
模式:
现在我们有了一个“高效”的汇编代码:
public static float F(float n)
{
return -n;
}
public static float G(float n)
{
return n * -1;
}
C.F(Single)
L0000: vzeroupper
L0003: vmovss xmm1, [C.F(Single)]
L000b: vxorps xmm0, xmm0, xmm1
L000f: ret
C.G(Single)
L0000: vzeroupper
L0003: vmulss xmm0, xmm0, [C.G(Single)]
L000b: ret
但我认为第一个和第二个问题是有效的:
为什么我们在这里有不同的 ASM 输出?
解决方案
输出不同,原因很简单,编译器不像人类那样“思考”而是遵循标准。
public static float F(float n)
{
return -n;
}
这意味着,要求编译器否定 n
并返回否定值。
这正是编译器所做的
vxorps xmm0, xmm0, xmm1 <--- change sign bit
public static float G(float n)
{
return n * -1;
}
这意味着进行乘法运算,而这正是编译器所做的
vmulss xmm0, xmm0, [C.G(Single)] <--- multiply
在您的世界(-n) == (n * -1)
中,但编译器对此有不同的看法。而且这两种表达方式不一样。所以(-n) != (n * -1)
和汇编输出是不同的。
对于浮点/双精度值来说,这甚至更糟(a * b * c) != (c * b * a)
……
至少不是默认情况下,您可以使用特殊的编译器标志使其相等,抱歉不记得它们到底是什么。
推荐阅读
- php - 基于两列的排名
- typescript - Typescript 接口/JSON 查询类型
- windows - 在 Windows 上使用 Spring Web 上传文件卡在大文件上
- mysql - 获取具有 Min() 值的记录的 id
- mysql - Mysql问题和使用函数last insert ID
- php - 如何在 Laravel 中扩展 PHPUnit?
- java - spark submit 抛出错误 java.lang.ClassNotFoundException: scala.runtime.java8.JFunction2$mcIII$sp
- python - 将 numpy 数组的每一行与第二个 numpy 数组的所有行相加(python)
- r - 基于规则的子集数据帧 - 序列
- c# - 记录不会插入表的最后一行