【发布时间】:2021-04-24 09:45:01
【问题描述】:
我有以下函数(我对其进行了一些清理以使其更易于理解),它将目标数组获取索引n 处的元素添加到它src1[i],然后将它与src2[i] 相乘(没什么花哨的):
static void F(long[] dst, long[] src1, long[] src2, ulong n)
{
dst[n] += src1[n];
dst[n] *= src2[n];
}
不,这会生成以下ASM:
<Program>$.<<Main>$>g__F|0_0(Int64[], Int64[], Int64[], UInt64)
L0000: sub rsp, 0x28
L0004: test r9, r9
L0007: jl short L0051
L0009: mov rax, r9
L000c: mov r9d, [rcx+8]
L0010: movsxd r9, r9d
L0013: cmp rax, r9
L0016: jae short L0057
L0018: lea rcx, [rcx+rax*8+0x10]
L001d: mov r9, rcx
L0020: mov r10, [r9]
L0023: mov r11d, [rdx+8]
L0027: movsxd r11, r11d
L002a: cmp rax, r11
L002d: jae short L0057
L002f: add r10, [rdx+rax*8+0x10]
L0034: mov [r9], r10
L0037: mov edx, [r8+8]
L003b: movsxd rdx, edx
L003e: cmp rax, rdx
L0041: jae short L0057
L0043: imul r10, [r8+rax*8+0x10]
L0049: mov [rcx], r10
L004c: add rsp, 0x28
L0050: ret
L0051: call 0x00007ffc9dadb710
L0056: int3
L0057: call 0x00007ffc9dadbc70
L005c: int3
你可以添加一堆东西,因为我可以保证n 将在合法范围之间:我可以使用指针。
static unsafe void G(long* dst, long* src1, long* src2, ulong n)
{
dst[n] += src1[n];
dst[n] *= src2[n];
}
现在这会生成更简单的ASM:
<Program>$.<<Main>$>g__G|0_1(Int64*, Int64*, Int64*, UInt64)
L0000: lea rax, [rcx+r9*8]
L0004: mov rcx, rax
L0007: mov rdx, [rdx+r9*8]
L000b: add [rcx], rdx
L000e: mov rdx, [rax] ; loads the value again?
L0011: imul rdx, [r8+r9*8]
L0016: mov [rax], rdx
L0019: ret
您可能已经注意到,那里有一个额外的MOV(我想,至少我无法解释为什么会在那里)。
问题
- 如何删除该行?在
C中,如果我没记错的话,我可以使用关键字restrict。 C#中有这样的关键字吗?很遗憾,我在互联网上找不到任何东西。
注意
- 这里是SharpLab 链接。
- 这是
C示例:
void
f(int64_t *dst,
int64_t *src1,
int64_t *src2,
uint64_t n) {
dst[n] += src1[n];
dst[n] *= src2[n];
}
void
g(int64_t *restrict dst,
int64_t *restrict src1,
int64_t *restrict src2,
uint64_t n) {
dst[n] += src1[n];
dst[n] *= src2[n];
}
这会生成:
f:
mov r10, rdx
lea rdx, [rcx+r9*8]
mov rax, QWORD PTR [rdx]
add rax, QWORD PTR [r10+r9*8]
mov QWORD PTR [rdx], rax ; this is strange. It loads the value back to [RDX]?
; shouldn't that be other way around? I don't know.
imul rax, QWORD PTR [r8+r9*8]
mov QWORD PTR [rdx], rax
ret
g:
mov r10, rdx
lea rdx, [rcx+r9*8]
mov rax, QWORD PTR [rdx]
add rax, QWORD PTR [r10+r9*8]
imul rax, QWORD PTR [r8+r9*8]
mov QWORD PTR [rdx], rax
ret
这里是Godbolt 链接。
【问题讨论】:
-
mov [rdx], rax存储(不加载)值回到dst[n],以防与src2[n]相同的内存位置(imul rax, [r8+r9*8]读取)。当我们知道它不可能是同一个位置时(因为消息来源承诺使用restrict),可以省略 store。不知道“其他方式”是什么意思,但除了比较地址和分支之外,我认为没有其他合理的方法可以解决可能的别名。 -
@PeterCordes 我的意思是:
MOV RAX, [RDX]。因为它正在使用RAX,所以我认为它应该再次加载RAX中的值。可能我在这里错了。 -
请注意 C# 和 C 编译器输出之间的区别。您的 C# 输出正在使用内存 destination 添加,因此它愚蠢地必须从它知道它刚刚写入 (
dst[n]) 以及src2[n]的位置重新加载.这很愚蠢,它知道那里有什么价值(因为我认为 C# 线程模型允许它假设没有其他线程写入该位置)。 (请注意,C# asm 无缘无故地将 LEA 结果从 RAX 复制到 RCX,因此[rax]和[rcx]实际上只是被取消引用两次的同一个指针。在 C 中没有限制,您只会得到一个额外的存储和相同的加载。
标签: c# assembly x86-64 restrict-qualifier