VC ++和ASM中的优化代码

时间:2017-07-12 05:35:59

标签: c++ visual-studio assembly visual-c++ optimization

晚上好。对不起,我使用了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 个答案:

答案 0 :(得分:2)

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

在x86-32版本中仅支持 内联汇编,即使在那里,它的功能也相当有限。特别是,你不能指定输入和输出,因此内联汇编的使用必然需要在寄存器和存储器之间来回传递大量值,这恰恰与编写高性能时的需求相反。码。除非通过手动发出机器代码,否则除了通过手动发出机器代码之外你不可能做任何其他事情,你应该避免使用内联汇编程序。它的最初目的是执行诸如生成OUT指令之类的操作,并在过时的8位和16位编程环境中调用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将了解您可以在各种体系结构上获得的性能。它在新的架构上变得越来越快(特别是在新的AMD处理器上;它在英特尔上仍然很迟钝),但它仍然具有相当大的延迟。仅仅因为它的一条指令并不意味着它在一个循环或类似的循环中运行。当您对大小进行优化并担心从库缓存中删除其他指令的库函数时,单个指令可能可能对代码密度有利,但是分区是一个足够慢的操作,这个通常没什么关系。实际上,除法很慢,以至于编译器非常努力地不使用它 - 只要有可能,它们就会乘以倒数,这会快得多。如果您真的需要快速进行乘法运算,那么您应该考虑使用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指令为无符号除法写入等效项。

不,你没有获得智能寄存器分配,但这对于前端的寄存器重命名来说并不是一件很重要的事情,通常可以完全忽略寄存器寄存器的移动(换句话说,{{ 1}}成为零延迟操作)。另外,MOV指令在其操作数方面是所以限制性的,因为它们被硬编码到IDIVRAX,它很漂亮调度程序无论如何都不可能将这些值保留在那些寄存器中,至少对于任何非平凡的代码都是如此。

请注意,一旦您编写了必要的代码来检查溢出的可能性,或者更糟糕的是 - 处理异常的代码 - 这很可能最终会执行与执行正确128位的库函数相同或更差的情况因此,您应该只是编写并使用它(直到Microsoft认为适合提供一个)。这可以是written in C(也可以参见Gnu编译器的RDX库函数的实现),这使得它成为内联的候选者,并且在优化器中可以更好地发挥作用。

答案 1 :(得分:0)

不,不可能这样做。 MSVC不支持x64版本的内联汇编。相反,你应该使用内在函数; 几乎一切都可用。遗憾的是,据我所知,内在函数中缺少128位idiv

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