内联 ASM:使用 MMX 在计时器上返回 NaN 秒
Posted
技术标签:
【中文标题】内联 ASM:使用 MMX 在计时器上返回 NaN 秒【英文标题】:Inline ASM: Use of MMX returns NaN seconds on timer 【发布时间】:2014-06-10 17:23:05 【问题描述】:问题
我正在尝试找出 mmx
或 xmm
寄存器是否更快地将数组的元素复制到另一个数组(我知道 memcpy()
但我需要此函数用于非常特定的目的)。
我的源代码如下。相关函数为copyarray()
。我可以将mmx
或xmm
寄存器分别与movq
或movsd
一起使用,结果是正确的。但是,当我使用 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 秒的主要内容,如果未能解决你的问题,请参考以下文章
Visual Studio 2008 内联汇编 关键字__asm出错
您如何在运行时使用 GCC 和内联 asm 检测 CPU 架构类型?