首页 文章

VC和ASM中的优化代码

提问于
浏览
3

晚上好 . 对不起,我使用了google tradutor . 我在x86上的VC中使用NASM,我正在学习如何在x64上使用MASM .

是否有任何方法可以指定每个参数的位置以及汇编函数的返回方式,使编译器能够以最快的方式将数据保留在那里?我们也可以指定使用哪些寄存器,以便编译器知道哪些数据仍然保存以充分利用它?

例如,由于没有内部函数可以应用精确的IDIV r / m64(汇编语言的64位有符号整数除法),我们可能需要实现它 . IDIV要求被除数/分子的低幅度部分在RAX中,RDX中的高值和任何寄存器或存储器区域中的除数/分母 . 最后,商在EAX中,其余在EDX中 . 因此,我们可能希望开发函数(我将实际情况举例说明):

void DivLongLongInt( long long NumLow , long long NumHigh , long long Den , long long *Quo , long long *Rem ){
    __asm(
        // Specify used register: [rax], specify pre location: NumLow --> [rax]
        reg(rax)=NumLow ,
        // Specify used register: [rdx], specify  pre location: NumHigh --> [rdx]
        reg(rdx)=NumHigh ,
        // Specify required memory: memory64bits [den], specify pre location: Den --> [den]
        mem[64](den)=Den ,
        // Specify used register: [st0], specify pre location: Const(12.5) --> [st0]
        reg(st0)=25*0.5 ,
        // Specify used register: [bh]
        reg(bh) ,
        // Specify required memory: memory64bits [nothing]
        mem[64](nothing) ,
        // Specify used register: [st1]
        reg(st1)
    ){
        // Specify code
        IDIV [den]
    }(
        // Specify pos location: [rax] --> *Quo
        *Quo=reg(rax) ,
        // Specify pos location: [rdx] --> *Rem
        *Rem=reg(rdx)
    ) ;
}

是否有可能做到至少接近于此的事情?谢谢你的帮助 .

如果没有办法做到这一点,那将是一种耻辱,因为它肯定是用汇编级功能实现高级函数的好方法 . 我认为它应该是C和ASM之间的一个简单接口,它应该已经存在并使汇编代码能够内嵌和高级嵌入,实际上就像简单的C代码一样 .

2 回答

  • 2

    As others have mentioned,MSVC在定位x86-64时不支持任何形式的内联汇编 .

    内联汇编仅在x86-32版本中受支持,即使在那里,它的功能也相当有限 . 特别是,您不能指定输入和输出,因此内联汇编的使用必然需要在寄存器和内存之间来回传递大量值,这恰恰与编写高性能代码时的需求相反 . 除非通过手动发出机器代码,否则除了通过手动发出机器代码之外你不可能做任何其他事情,你应该避免使用内联汇编程序 . 它最初的目的是在过时的8位和16位编程环境中执行生成 OUT 指令和调用ROM BIOS中断等操作 . 为了兼容性目的,它使它成为32位编译器,但团队使用了64位 .

    Intrinsics现在是推荐的解决方案,因为这些优化器可以更好地发挥作用 . 实际上,您需要编译器生成的任何SIMD代码都可以使用内在函数来完成,就像大多数其他针对x86的编译器一样,因此您不仅可以获得更好的代码,而且还可以获得稍微更轻松的代码 .

    即使在支持extended asm blocks的Gnu风格的编译器上,它们为您提供了所需的输入/输出操作数功能,但仍然有lots of good reasons to avoid the use of inline asm . Intrinsics仍然是一个更好的解决方案,就像找到一种方法来表示你想要的C并说服编译器生成你希望它发出的汇编代码 .

    唯一的例外是没有内在函数的情况 . 不幸的是, IDIV 指令是其中一种情况 . (有内在函数可用于128位乘法 . 它们有各种名称:Windows-specificcompiler-specific . )

    在支持128位整数类型作为64位目标扩展的Gnu编译器上,您可以让编译器为您生成代码:

    __int128_t dividend = 1234;
    int64_t    divisor  = 64;
    int64_t    quotient = (dividend / divisor);
    

    现在,这通常被编译为对其函数执行128位除法的调用,而不是返回64位商的内联 IDIV 指令 . 据推测,这是因为需要处理溢出,as David mentioned . 实际上,它比那更糟糕 . 没有C或C实现可以使用 DIV / IDIV 指令,因为它们不符合要求 . 这些指令将导致溢出异常,而标准表示结果应该被截断 . (使用乘法,你会得到内联 IMUL / MUL 指令,因为它们没有溢出问题,因为它们返回128位结果 . )

    这实际上并不像你想象的那么大 . 您似乎假设64位 IDIV 指令非常快 . 事实并非如此 . 虽然实际数字取决于被除数的绝对值中的有效位数,但如果您确实需要128位整数的范围,则您的值可能非常大 . 查看Agner Fog's instruction tables将了解您可以在各种体系结构上获得的性能 . 它虽然英特尔仍然表现不佳,但它仍有相当大的延迟 . 仅仅因为它意味着它在一个周期或类似的东西中运行 . 当你重要的时候,单个指令可能对代码密度有益 . 实际上,除法很慢,以至于编译器非常努力地不使用它 - 只要有可能,它们就会乘以倒数,这会快得多 . 如果你真的需要快速进行乘法运算,你应该考虑使用SIMD指令并行化,这些指令都有内在函数可用 .

    回到MSVC(虽然我在上一段中所说的一切仍然适用,当然),没有128位整数类型,所以如果你需要实现这种类型的除法,你需要在外部程序集中编写代码模块并将其链接进去 . 代码非常简单,Visual Studio具有出色的内置支持,可以使用MASM汇编代码并将其直接链接到项目中:

    ; Windows 64-bit calling convention passes parameters as follows:
    ; RCX == first  64-bit integer parameter (low bits of dividend)
    ; RDX == second 64-bit integer parameter (high bits of dividend)
    ; R8  == third  64-bit integer parameter (divisor)
    ; R9  == fourth 64-bit integer parameter (pointer to remainder)
    Div128x64 PROC
        mov  rax, rcx
        idiv r8          ; 128-bit divide (RDX:RAX / R8)
        mov  [r9], rdx   ; store remainder
        ret              ; return, with quotient in RDX:RAX
    Div128x64 ENDP
    

    然后你只需在C代码中将其原型化为:

    extern int64_t Div128x64(int64_t  loDividend,
                             int64_t  hiDividend,
                             int64_t  divisor,
                             int64_t* pRemainder);
    

    你完成了根据需要调用它 .

    可以使用 DIV 指令为无符号除法写入等效项 .

    不,你在前端的寄存器重命名真的很重要,通常可以完全忽略寄存器寄存器的移动(换句话说, MOV 成为零延迟操作) . 另外, IDIV 指令在其操作数方面是如此限制,因为它们被硬编码为 RAXRDX ,调度程序不太可能将这些值保存在这些寄存器中,至少对于任何非平凡的部分而言都是如此 . 代码

    请注意,一旦你编写了必要的代码来检查溢出的可能性,或者更坏 - 处理异常的代码 - 这很可能最终会执行与执行正确128位除法的库函数相同或更差,所以你可以说只是编写和使用它(直到微软认为适合提供一个) . 这可以是written in C(也可以参见Gnu编译器的 __divti3 库函数的实现),这使得它成为内联的候选者,并且与优化器一起玩得更好 .

  • 0

    不,这是不可能的 . MSVC不支持x64版本的内联汇编 . 相反,你应该使用内在函数; almost 一切都可以 . 可悲的是,据我所知,内在函数中缺少128位 idiv .

    注意:您可以使用两个 mov 来解决您的问题(将输入放在正确的寄存器中) . 而你不应该担心;当前CPU处理 mov very . 将 mov 放入代码可能不会减慢速度 . divmov 相比非常昂贵,所以它并不重要 .

相关问题