内联 ASM:使用 MMX 在计时器上返回 NaN 秒

Posted

技术标签:

【中文标题】内联 ASM:使用 MMX 在计时器上返回 NaN 秒【英文标题】:Inline ASM: Use of MMX returns NaN seconds on timer 【发布时间】:2014-06-10 17:23:05 【问题描述】:

问题

我正在尝试找出 mmxxmm 寄存器是否更快地将数组的元素复制到另一个数组(我知道 memcpy() 但我需要此函数用于非常特定的目的)。

我的源代码如下。相关函数为copyarray()。我可以将mmxxmm 寄存器分别与movqmovsd 一起使用,结果是正确的。但是,当我使用 mmx 寄存器时,我使用的任何计时器(clock()QueryPerformanceCounter)都会返回 NaN

编译:gcc -std=c99 -O2 -m32 -msse3 -mincoming-stack-boundary=2 -mfpmath=sse,387 -masm=intel copyasm.c -o copyasm.exe

这是一个非常奇怪的错误,我不明白为什么使用mmx 寄存器会导致计时器返回NaN 秒,而在完全相同的代码中使用xmm 寄存器会返回一个有效的时间值

编辑

使用xmm 寄存器的结果:

Elapsed time: 0.000000 seconds, Gigabytes copied per second: inf GB
Residual = 0.000000
  0.937437    0.330424    0.883267    0.118717    0.962493    0.584826    0.344371    0.423719
  0.937437    0.330424    0.883267    0.118717    0.962493    0.584826    0.344371    0.423719

使用mmx 注册的结果:

Elapsed time: nan seconds, Gigabytes copied per second: inf GB
Residual = 0.000000
  0.000000    0.754173    0.615345    0.634724    0.611286    0.547655    0.729637    0.942381
  0.935759    0.754173    0.615345    0.634724    0.611286    0.547655    0.729637    0.942381

源代码

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <x86intrin.h>
#include <windows.h>

inline double 
__attribute__ ((gnu_inline))        
__attribute__ ((aligned(64))) copyarray(
            double* restrict dst,
            const double* restrict src,
            const int n)

    // int i = n;
    // do 
        // *dst++ = *src++;
        // i--;
        //  while(i);

            __asm__ __volatile__
            (
                "mov    ecx, %[n]                   \n\t"
                "mov    edi, %[dst]                 \n\t"
                "mov    esi, %[src]                 \n\t"
                "xor    eax, eax                    \n\t"
                "sub    ecx,1                       \n\t"
                "L%=:                               \n\t"
                "movq   mm0, QWORD PTR [esi+ecx*8]  \n\t"
                "movq   QWORD PTR [edi+ecx*8], mm0  \n\t"
                "sub    ecx, 1                      \n\t"
                "jge    L%=                         \n\t"
                : // no outputs
                : // inputs
                [dst] "m" (dst),
                [src] "m" (src),
                [n] "g" (n)
                : // register clobber
                "eax","ecx","edi","esi",
                "mm0"
            );


void printarray(double* restrict a, int n)

    for(int i = 0; i < n; ++i) 
        printf("  %f  ", *(a++));
    
    printf("\n");


double residual(const double* restrict dst,
                const double* restrict src,
                const int n)

    double residual = 0.0;

    for(int i = 0; i < n; ++i)
        residual += *(dst++) - *(src++);

    return(residual);


int main()

    double *A = NULL;
    double *B = NULL;
    int n = 8;
    double memops;
    double time3;
    clock_t time1;
    // LARGE_INTEGER frequency, time1, time2;
    // QueryPerformanceFrequency(&frequency);
    int trials = 1 << 0;


    A = _mm_malloc(n*sizeof(*A), 64);
    B = _mm_malloc(n*sizeof(*B), 64);

    srand(time(NULL));
    for(int i = 0; i < n; ++i)
        *(A+i) = (double) rand()/RAND_MAX;

            // QueryPerformanceCounter(&time1);

    time1 = clock();
    for(int i = 0; i < trials; ++i)
        copyarray(B,A,n);

        // QueryPerformanceCounter(&time2);

    // time3 = (double)(time2.QuadPart - time1.QuadPart) / frequency.QuadPart;
    time3 = (double) (clock() - time1)/CLOCKS_PER_SEC;

    memops = (double) trials*n/time3*sizeof(*A)/1.0e9;
    printf("Elapsed time: %f seconds, Gigabytes copied per second: %f GB\n",time3, memops);
    printf("Residual = %f\n",residual(B,A,n));
    printarray(A,n);
    printarray(B,n);

    _mm_free(A);
    _mm_free(B);

【问题讨论】:

在混合 MMX 和浮点时必须小心。请改用 SSE。 @PaulR,会有什么问题? MMX 寄存器不是 x87 浮点寄存器的子集吗? OK - 阅读 this page 上标题为“MMX - 状态管理”的部分 - 在下一次执行任何浮点运算之前,请注意在任何 MMX 指令之后对 emms 指令的要求。 MMX 寄存器别名为 FP 寄存器。所以使用一个会破坏另一个。 我知道这只是测试代码,但你可以做得更好:1)将 src 和 dst 的约束更改为“S”和“D”,省略 MOV 和 clobbers,更改所有 refs到 %[src]/%[dst]。编译器会在方便的时候将 ptrs 移入并可以在其他代码中重新使用它们。 2) 将 n 设为输出并使用“+r”(省略 MOV & clobber,将 ecx 更改为 %[n])。让编译器选择一个临时注册。 3) 重要提示:由于您正在读取+写入内存和更改标志,因此这些应该被破坏。看起来只需一点工作,这可以变成 5 个 asm 指令,并且只有这个用于 clobber:“cc”、“memory”、“mm0”。 FWIW。 【参考方案1】:

将 MMX 与浮点混合使用时必须小心 - 如果可能,请改用 SSE。如果您必须使用 MMX,请阅读 this page 上标题为“MMX - 状态管理”的部分 - 请注意在任何 MMX 指令之后的 emms instruction 要求,然后再执行任何浮点运算。

【讨论】:

以上是关于内联 ASM:使用 MMX 在计时器上返回 NaN 秒的主要内容,如果未能解决你的问题,请参考以下文章

并发使用中内联 asm 的设计元素

Visual Studio 2008 内联汇编 关键字__asm出错

您如何在运行时使用 GCC 和内联 asm 检测 CPU 架构类型?

内联 asm 到 x64 - 理解

GCC 内联汇编错误:变量 '%al' 的 asm 说明符与 asm clobber 列表冲突

在 C++ 内联 asm 中使用基指针寄存器