Pointers asm指针,我做错了什么?

Pointers asm指针,我做错了什么?,pointers,assembly,Pointers,Assembly,这是我家庭作业代码的一部分,它工作正常,但我想知道如何将交换改为使用寄存器而不是dword PTR。我最初有: int sort(int* list) { __asm { mov esi, [list]; mov eax, dword ptr[esi + edx * 4]; store pointer to eax? mov edi, dword ptr[esi + 4 + edx * 4]; store poi

这是我家庭作业代码的一部分,它工作正常,但我想知道如何将交换改为使用寄存器而不是dword PTR。我最初有:

int sort(int* list)
{
    __asm
    {
        mov esi, [list];
            mov eax, dword ptr[esi + edx * 4];   store pointer to eax?
            mov edi, dword ptr[esi + 4 + edx * 4]; store pointer to edi?
            jmp swap;

swap:
        push dword ptr[esi + edx * 4];
        mov dword ptr[esi + edx * 4], edi;      
        pop dword ptr[esi + 4 + edx * 4];
但这实际上并没有交换任何东西,传入的数组并没有改变。如何让我重写代码,使swap语句看起来像这样?在上面的swap语句中,我尝试将[]放在eax附近,但这也不起作用。

有三条指令(正如Kerrek SB所说),只有一个寄存器(eax):

或者,使用数组作为参数:

int exchange ()
{ int list[5] = {1,5,2,4,3};
  __asm { mov edx, 0
          lea esi, list
          // SWAP WITH THREE INSTRUCTIONS.
            mov  eax, [esi + edx * 4]
            xchg [esi + 4 + edx * 4], eax
            mov  [esi + edx * 4], eax
            // NOW LIST =  {5,1,2,4,3};
        }
}
这就是它的名称:

int exchange ( int * list )
{  __asm { mov edx, 0
           mov esi, list
           // SWAP WITH THREE INSTRUCTIONS.
           mov  eax, [esi + edx * 4]
           xchg [esi + 4 + edx * 4], eax
           mov  [esi + edx * 4], eax
           // LIST =  {5,1,2,4,3};
         }
}
有三条指令(如Kerrek SB所说)和一个寄存器(EAX):

或者,使用数组作为参数:

int exchange ()
{ int list[5] = {1,5,2,4,3};
  __asm { mov edx, 0
          lea esi, list
          // SWAP WITH THREE INSTRUCTIONS.
            mov  eax, [esi + edx * 4]
            xchg [esi + 4 + edx * 4], eax
            mov  [esi + edx * 4], eax
            // NOW LIST =  {5,1,2,4,3};
        }
}
这就是它的名称:

int exchange ( int * list )
{  __asm { mov edx, 0
           mov esi, list
           // SWAP WITH THREE INSTRUCTIONS.
           mov  eax, [esi + edx * 4]
           xchg [esi + 4 + edx * 4], eax
           mov  [esi + edx * 4], eax
           // LIST =  {5,1,2,4,3};
         }
}

部分混淆可能是函数如何接收其输入。如果您使用asm编写整个函数,而不是使用MSVC特定语法内联,则会告诉您参数将在堆栈上(对于32位x86代码)。还有一个调用约定文档,涵盖x86和x86-64的各种不同调用约定

无论如何

xchg
可能看起来正是执行交换所需的指令。如果您真的需要交换两个寄存器的内容,那么它的性能与3
mov
指令非常相似,否则需要这些指令,但不需要临时寄存器。然而,实际上很少需要交换两个寄存器,而不仅仅是覆盖一个,或者将旧值保存到其他地方。另外,3
mov-reg,reg
在常春藤桥/Haswell上会更快,因为他们不需要执行单元;他们只是在寄存器重命名阶段处理它(延迟为0)

对于交换两个内存位置的内容,它至少比使用
mov
加载/存储慢25倍,因为隐式
LOCK
前缀迫使CPU确保所有其他内核立即得到更新,而不是仅仅写入一级缓存

你需要做的是两次装载和两次存储

最简单的形式(2个负载,2个存储,在一般情况下工作)为

使用更复杂的寻址模式(例如
[rdi+rax*4]
,您可以将
列表[rax]
列表[rbx]
交换)

如果内存位置相邻,您可以使用更大的负载同时加载这两个位置,然后旋转以交换。e、 g

# void swap34(int *array)

swap34:
 # 32bit: mov edi, [esp+4]   # [esp] has the return address
 # 64bit windows: mov rdi, rcx  # first function arg comes in rcx
# array pointer in rdi, assuming 64bit SystemV (Linux) ABI.
mov eax, [rdi+8]    # load array[3]
mov ebx, [rdi+12]   # load array[4]
mov [rdi+12], eax   # store array[4] = tmp1
mov [rdi+8],  ebx   # store array[3] = tmp2
ret
我相信这3条指令的实际运行速度将超过
rol[rdi+4],32
。(与内存操作数一起旋转,
imm8
计数在Intel Sandybridge上为4 uops,每2个周期的吞吐量为1。加载/旋转/存储为3 uops,并且应维持每周期1 uops。内存操作数版本使用更少的指令字节。不过,它不会在寄存器中保留任何值。通常在实际代码中,您需要执行某些操作。)使用其中一个值进行进一步调整。)

我能想到的使用更少指令的唯一其他方法是,如果将
rsi
rdi
指向要交换的值。那你就可以

# int *array in rdi
mov  rax, [rdi+4]  # load 2nd and 3rd 32bit element
rol  rax, 32       # rotate left by half the reg width
mov  [rdi+4], rax  # store back to the same place
这将比2次加载/2次存储慢得多,并且
movsd
增量
rsi
rdi
。在这里保存指令实际上会导致代码速度变慢,并且在最近的英特尔设计中,uop缓存中会占用更多的空间。(没有
rep
前缀的
movsd
绝不是一个好选择。)

另一条从一个内存位置读取并写入另一个内存位置的指令是带有内存操作数的
pop
push
,但只有当堆栈指针已经指向要交换的值之一,并且您不关心更改堆栈指针时,该指令才起作用


不要弄乱堆栈指针。理论上,您可以将堆栈指针保存在某个位置,并将其用作寄存器不足的循环的另一个GP寄存器,但前提是您不需要调用任何东西,并且当您有
rsp
不指向堆栈时,不会发生任何可能尝试使用堆栈的异步情况。说真的,即使是手工编写的性能优化asm,也很少使用堆栈指针来进行正常使用,所以真的忘了我提到过它

部分混淆可能是函数如何接收输入。如果您使用asm编写整个函数,而不是使用MSVC特定语法内联,则会告诉您参数将在堆栈上(对于32位x86代码)。还有一个调用约定文档,涵盖x86和x86-64的各种不同调用约定

无论如何

xchg
可能看起来正是执行交换所需的指令。如果您真的需要交换两个寄存器的内容,那么它的性能与3
mov
指令非常相似,否则需要这些指令,但不需要临时寄存器。然而,实际上很少需要交换两个寄存器,而不仅仅是覆盖一个,或者将旧值保存到其他地方。另外,3
mov-reg,reg
在常春藤桥/Haswell上会更快,因为他们不需要执行单元;他们只是在寄存器重命名阶段处理它(延迟为0)

对于交换两个内存位置的内容,它至少比使用
mov
加载/存储慢25倍,因为隐式
LOCK
前缀迫使CPU确保所有其他内核立即得到更新,而不是仅仅写入一级缓存

你需要做的是两次装载和两次存储

最简单的形式(2个负载,2个存储,在一般情况下工作)为

使用更复杂的寻址模式(例如
[rdi+rax*4]
,您可以将
列表[rax]
列表[rbx]
交换)

如果内存位置相邻,您可以使用更大的负载同时加载这两个位置,然后旋转以交换。e、 g

# void swap34(int *array)

swap34:
 # 32bit: mov edi, [esp+4]   # [esp] has the return address
 # 64bit windows: mov rdi, rcx  # first function arg comes in rcx
# array pointer in rdi, assuming 64bit SystemV (Linux) ABI.
mov eax, [rdi+8]    # load array[3]
mov ebx, [rdi+12]   # load array[4]
mov [rdi+12], eax   # store array[4] = tmp1
mov [rdi+8],  ebx   # store array[3] = tmp2
ret
我相信
Push Eax
Mov  Eax, Edi
Pop  Edi
Mov  dword ptr[esi + 4 + edx * 4], Eax
Mov  dword ptr[esi + edx * 4], Edi
mov eax, [esi + edx * 4]  
mov edi, [esi + 4 + edx * 4]