您能给我一些可以编译到repstos的示例C代码吗?

00401059  /$  55            PUSH EBP
0040105A  |.  8BEC          MOV EBP,ESP
0040105C  |.  83EC 5C       SUB ESP,5C
0040105F  |.  57            PUSH EDI
00401060  |.  66:A1 E068400>MOV AX,WORD PTR DS:[4068E0]
00401066  |.  66:8945 B0    MOV WORD PTR SS:[EBP-50],AX
0040106A  |.  B9 13000000   MOV ECX,13
0040106F  |.  33C0          XOR EAX,EAX
00401071  |.  8D7D B2       LEA EDI,DWORD PTR SS:[EBP-4E]
00401074  |.  F3:AB         REP STOS DWORD PTR ES:[EDI]
00401076  |.  66:AB         STOS WORD PTR ES:[EDI]
00401078  |.  8D4D B0       LEA ECX,DWORD PTR SS:[EBP-50]
0040107B  |.  51            PUSH ECX                                 ; /Arg2
0040107C  |.  8D55 A4       LEA EDX,DWORD PTR SS:[EBP-5C]            ; |
0040107F  |.  52            PUSH EDX                                 ; |Arg1
00401080  |.  E8 7BFFFFFF   CALL 00401000                            ;             


这只是我的示例。

#1 楼

让我们开始解释每个指令的作用。 REP OPD的工作方式如下:

          for (; ecx > 0; ecx--) OPD


该指令将在递减ECX直到其达到0时重复操作数。请注意,代码中ECX设置为13(地址0040106A) )。另一方面,STOS OPD将AL或AX或EAX的值存储在给定的内存操作数中。寄存器的大小由内存位置的大小定义,因此由代码中的DWORD定义。

总体上,这两个指令组合在一起,以创建一个将数据存储在内存中的循环。在C形式中,如果我们想将字节数组初始化为0(例如memset),我们可以这样做:

    unsigned char t[MAX_CHAR];

    for (int i = 0; i < MAX_CHAR; i++)
         t[i] = 0;
多个程序集等效项。这主要取决于编译器和指定的优化级别。
基于REP STOS的一个变体可能是:

    mov ecx, MAX_CHAR  //Initialize ecx to the number of iterations desired
    xor eax, eax       //Initialize eax to 0  
    rep stos [t + ecx] //Store the value of eax in t[i] where i = ecx


这两个汇编代码完全相似。唯一的区别是,就周期而言,一个将比另一个花费更多。换句话说,一个比另一个快。如何定义最好的?如果在第162页上查看Agner Fog的指令表,您会注意到REP STOS延迟在最坏的情况下为n(n是迭代数)。 XOR REG,SAME将花费我们0.25个周期,而MOV R,IMM将花费我们0.5个周期。总体而言,第一个汇编代码的性能可以评估为:延迟= n + 0.75个周期(1000次迭代〜1000.75个周期)。 />
    mov ecx, MAX_CHAR   //Initialize ecx to the number of iterations
    xor eax, eax        //Initialize eax to 0
    loop0:              //Define loop label
    mov [t + ecx], eax  //Copy eax into t[i] where i = ecx 
    dec ecx             //Decrement ecx ==> ecx = ecx - 1
    jnz loop0           //Jump only if previous result (ecx - 1) isn't 0


在这种情况下,我们得到的延迟为4 * n + 0.75。

现在,您可能会认为第一个代码比第二个代码快,因为n <4n。别忘了英特尔架构正在流水线化,还有其他一些奇怪之处。不过,我可以向您保证的是,与第二个代码相比,第一个代码非常慢。为什么? REP STOS是微编码的(Fused µOps列)。意思是,这不是真正的硬件指令。它实质上是通过将多个电路(此处为两个)组合在一起而制成的,而过去使用奔腾III和IV可以节省您的时间。这种方法的问题在于它在管道中的作用不大。
第二个汇编代码将更快,因为所有指令都是硬编码的。意思是,每个电路都有一个特殊的电路,无需结合其他电路来执行所需的任务。因此,这些指令可以很容易地流水线化,因此计算出的4n总体延迟可以下降到远低于n的水平(如果一切都被正确缓存)。对于所有三个指令,整个循环迭代将下降到大约0.75个周期。再加上分支预测器正确使用时的额外奖励。

我必须提醒您,这些指令在CPU难以高效运行循环构造的时代才得以实现。由于分支太多和缓存太小,尤其适合于字符串处理。随着高效的分支预测器和更大/更复杂的缓存系统的出现,这些指令以某种方式变得过时了。除非您是在相当老的CPU上运行代码或使用相当老的编译器(例如Turbo C),否则很少有编译器会基于REP STOS或REP LODS生成代码。我希望我的帖子有助于阐明这些说明以及如何使用它们。让我知道您是否需要更多详细信息或更多说明。

评论


我的assembly_code上有for循环吗?

–初学者
16年1月1日,下午2:34

不!没有明确。汇编代码中没有跳转。但是REP STOS是一个循环。

– Yaspr
16年1月1日在7:55

您是“ OPD”的缩写吗? (此外,请大胆使用。除此之外:好的答案,我已经赞成。)

–杂件
16 Dec 1'在9:57



OPD ==>操作数(在本例中为STOS。我把OPD'因为REP也可以与LODS一起使用。粗体用来表示重要元素,仅此而已。

– Yaspr
16年1月1日于11:32

#2 楼

通常在memcpy()memset()等操作期间。类似于下面的代码:

#define BUF_SIZE 1024

wchar _t foo[BUF_SIZE]  = "yyyyyyyyy";
wchar _t blah[BUF_SIZE] = {0};   
wchar _t bar[BUF_SIZE];

memcpy(blah, BUF_SIZE, foo);
memset(bar, BUF_SIZE, 0);


UPDATE

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#pragma intrinsic (memset , memcpy)
void somefunc() { 
  char *foo = "yabba dabba choo";
  char *blah = (char *)malloc(0x20);
  if(blah) {
    memset(blah,0,0x20);  <<-- line no 9
    memcpy(blah,foo,0x20);
    printf("%s\n" , blah);
  }
}
void main (void) {
  somefunc();
}


编译有/ O1优化vc ++

cl /nologo /Zi /W4 /O1 /analyze /EHsc /Ferepstos__opt.exe repstos.cpp  /link /RELEASE


拆卸

cdb -c "uf repstos__opt!somefunc;q" repstos__opt.exe | grep initial -A 40

0:000> cdb: Reading initial command 'uf repstos__opt!somefunc;q'
repstos__opt!somefunc:
010a1261 6a20            push    20h
010a1263 e8c69a0100      call    repstos__opt!malloc (010bad2e)
010a1268 8bd0            mov     edx,eax
010a126a 59              pop     ecx
010a126b 85d2            test    edx,edx
010a126d 7426            je      repstos__opt!somefunc+0x34 (010a1295)

repstos__opt!somefunc+0xe:
010a126f 56              push    esi
010a1270 57              push    edi
010a1271 6a08            push    8
010a1273 59              pop     ecx
010a1274 33c0            xor     eax,eax
010a1276 8bfa            mov     edi,edx
010a1278 f3ab            rep stos dword ptr es:[edi] <<----
010a127a 6a08            push    8
010a127c 59              pop     ecx
010a127d 8bfa            mov     edi,edx
010a127f beb0c10d01      mov     esi,offset repstos__opt!`string' (010dc1b0)
010a1284 52              push    edx
010a1285 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
010a1287 68c4c10d01      push    offset repstos__opt!`string' (010dc1c4)
010a128c e836000000      call    repstos__opt!printf (010a12c7)
010a1291 59              pop     ecx
010a1292 59              pop     ecx
010a1293 5f              pop     edi
010a1294 5e              pop     esi

repstos__opt!somefunc+0x34:
010a1295 c3              ret
quit:


9号线的拆卸

0:000> lsa .,4,2
     9:     memset(blah,0,0x20);
    10:     memcpy(blah,foo,0x20);
0:000> u `repstos__opt!repstos.cpp:9`
repstos__opt!somefunc+0xe [xxxx\repstos.cpp @ 9]:
002c126f 56              push    esi
002c1270 57              push    edi
002c1271 6a08            push    8
002c1273 59              pop     ecx
002c1274 33c0            xor     eax,eax
002c1276 8bfa            mov     edi,edx
002c1278 f3ab            rep stos dword ptr es:[edi] <<<---
002c127a 6a08            push    8
0:000>


评论


你确定?我没有看到任何REP或stos。

–初学者
16-11-30在16:02

#include #include int main(){char str1 [] =“示例字符串”; char str2 [40]; char str3 [40]; memcpy(str2,str1,strlen(str1)+1); memcpy(str3,“复制成功”,16); printf(“ str1:%s \ nstr2:%s \ nstr3:%s \ n”,str1,str2,str3);返回0; }

–初学者
16-11-30在16:02

您需要进行优化编译,并且可以内联函数或使用__forceinline编译器很聪明,它可以计算成本效益分析,并根据净收益使memset成为函数或内联,例如使用perror编译器进行编辑可能会确定repstos最佳,可以将memset调用为函数,但其​​他一些与@perror相同的代码却被编辑,但是buffsize较小,例如0x20编译器可能不会将memset调用为函数,但会将其内联为repstos如今,即使repstos已过时,编译器使用movups xmm指令和移动双字

– blabb
16-11-30在19:34