内联 asm 缓冲区循环

Posted

技术标签:

【中文标题】内联 asm 缓冲区循环【英文标题】:inline asm buffer loop 【发布时间】:2009-12-19 07:32:13 【问题描述】:

好的,这是我的问题。我想循环使用一个简单的字符缓冲区 内联 asm 和 VC++;

我的议程是创建一个循环,尽可能快地从内存中读取信息

这是我的代码

char buffer[howmany];
memset(buffer,33,howmany);
char arr = 0;
__asm

MOV     eax,seg buffer ;operand size conflict
MOV     eds,eax
MOV ecx,[howmany]
MOV     ebx,ip
MOV     arr, ES::buffer[ecx] ;operand size conflict
                             ;inline assembler syntax error in 'third operand'
                             ;found 'bad token'
LOOP    ebx ;inline assembler syntax error in 'opcode'; found 'bad token'
                ; 'LOOP' : identifier is reserved word

我对组装很陌生,但这似乎是正确的,但它不起作用?我不确定为什么。提前致谢。

最终解决方案

__asm

    LEA         esi, buffer
    MOV      ecx,howmany
    buf_loop:   
    mov         eax, [esi]
    inc         esi
    dec         ecx
    jnz         buf_loop

【问题讨论】:

成功了吗?你应该做的第一件事是测试它。然后,如果它不起作用并且您无法弄清楚原因,请回到我们这里。 有比循环更好的方法,检查我编辑的答案。 我怀疑闯入组装会让你在这里获得任何额外的速度。根据具体情况,您的瓶颈将是内存 IO 和缓存命中等。如果您的目标是用汇编语言编写,请忽略我的评论,但如果您的目标是速度,那么 asm 在这里不会有太大优势。用一粒盐和个人资料来接受我的建议。 @amwinter 已经有一段时间了,但是闯入 asm 让我的速度有了很大的提升。 【参考方案1】:

首先,没有 EDS,只有 DS。即使在 32 位模式下,段寄存器仍然是 16 位。

其次,除非您正在使用像 DOS 扩展器这样的古老系统,或者非常不寻常的系统(与 Windows、Linux、OS/X、BSD 等典型的桌面/服务器操作系统有很大不同),否则您应该'在任何情况下都不要修改任何段寄存器。大多数当前系统使用“平面”内存模型,其中操作系统将所有1段寄存器设置为基数为 0 和内存顶部的限制,因此您永远没有任何理由修改任何一个。

不幸的是,虽然说你的代码是错误的很容易,但说什么是对的却有点困难——你对你想做的事情说得不够多。现在,看起来您正在从缓冲区复制,但每次通过循环时,您都会覆盖您在上一次迭代中写入的值,所以您最好只复制最后一个单词并完成。为了通过缓冲区循环完成很多事情,您需要将其复制到相同(或更大)大小的目标缓冲区:

mov ecx, howmany
mov esi, offset FLAT:source
mov edi, offset FLAT:dest
rep movsd

正如其他人已经指出的那样,循环指令的操作数是标签,而不是寄存器。他们似乎没有指出的是,对于现代 CPU(比原始 Pentium 更新的任何东西),您通常希望完全避免使用 LOOP 指令。然而,为了争论,执行上述移动的循环如下所示:

    mov ecx, howmany
    mov esi, offset FLAT:source
    mov edi, offset FLAT:dest
move_loop:
    lodsd
    stosd
    loop move_loop

对于现代 CPU,通常最好使用更多但更简单的指令。

; same setup as above
move_loop:
    mov eax, [esi]
    mov [edi], eax
    inc esi
    inc edi
    dec ecx
    jnz move_loop

另一方面,在这种情况下,这不太重要——除非它都适合缓存,否则像这样的块移动几乎总是受到内存带宽的限制——移动不会得到 快得多,但要获得最后一点改进,您需要使用 SSE 指令/寄存器。

编辑:最后一个细节。 VC++(以及其他)不允许您在 _asm 块内定义标签,因此如果您需要标签,您可以执行以下操作:

_asm 
    mov ecx, howmany
    mov esi, offset FLAT:source
    mov edi, offset FLAT:dest


move_loop:

_asm 
    lodsd
    stosd
    loop move_loop

1嗯,不是所有的——FS 和可能的 GS 都不会这样,但 CS、DS、ES 和 SS 会。无论如何,您都不想更改它们中的任何一个(实际上,尝试这样做通常只会让您的程序关闭)。

【讨论】:

vc 确实让我在 __asm 块内定义了一个标签......另外,还有什么比循环更好的方法?正如我所说,我对汇编还很陌生,很难找到关于使用比 16 位架构更新的汇编的信息。【参考方案2】:

循环指令需要一个目标标签(它使用相对跳转,汇编器会自动计算差异),所以更像这样:

char buffer[howmany];
memset(buffer,33,howmany);
char arr = 0;
__asm

MOV     eax, buffer ; buffer is a pointer, thus eax contains address
MOV     eds, eax
MOV     ecx, howmany ; You probably want the value of howmany, not address
buf_loop:
MOV     arr, [eax] ; segments are for 16bit DOS code, just dereference the array pointer in
INC     TYPE buffer ; Move to next element
LOOP    buf_loop

【讨论】:

不过,您可以循环到特定的段偏移量。 根据您想要完成的猜测,我已尽力修复您的代码。它有很多缺陷。【参考方案3】:

这是保护模式代码吗?如果是这样,请不要乱用EDS

循环指令必须给出一个跳转目标标签。无法使用ip(或eip)加载ebx,因为ip 不是正确的操作数。

【讨论】:

我的缓冲区很大,所以我正在移动到它所在的段来完成这项工作。 如果您处于平面地址保护模式,则缓冲区位于数据段中。加载 EDS 是一个昂贵的指令:除非绝对必要,否则不要这样做。如果该值确实需要更改,请务必在完成后将 EDS 设置回原来的值。【参考方案4】:

LOOP ebx 行看起来很可疑,您不应该指定要跳转到的标签吗?

label:
//some code
LOOP label

另外,如果您想避免循环,请查看

REP MOVS

REP STOS

说明。

【讨论】:

我存储了数组开头的内存位置...MOV ebx,ip...所以它循环回到那个位置并抓取下一个字符 @kelton52 不行,不能用寄存器的值作为标签。 不过,您可以循环到特定的段偏移量。【参考方案5】:

我没有时间写出来,但是使用 SSE2 寄存器,您可以在每个循环中读取 16 个字节。您必须首先有一个基于字节的循环才能对齐(以及之后)

【讨论】:

以上是关于内联 asm 缓冲区循环的主要内容,如果未能解决你的问题,请参考以下文章

使用 C++ 中的内联汇编对缓冲区中的数字求和

模板的OpenGL修剪/内联轮廓

__asm__ gcc 调用内存地址

手写 asm.js - 你如何跟踪堆中的 javascript 对象?

c++缓冲池循环队列实现

在linux中分配物理内存缓冲区