rep
和stos
的示例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生成代码。我希望我的帖子有助于阐明这些说明以及如何使用它们。让我知道您是否需要更多详细信息或更多说明。
#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
–初学者
16-11-30在16:02
您需要进行优化编译,并且可以内联函数或使用__forceinline编译器很聪明,它可以计算成本效益分析,并根据净收益使memset成为函数或内联,例如使用perror编译器进行编辑可能会确定repstos最佳,可以将memset调用为函数,但其他一些与@perror相同的代码却被编辑,但是buffsize较小,例如0x20编译器可能不会将memset调用为函数,但会将其内联为repstos如今,即使repstos已过时,编译器使用movups xmm指令和移动双字
– blabb
16-11-30在19:34
评论
我的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