C# 警告C4799:功能没有EMMS指令 我试图创建C++应用程序,它使用了包含C++代码和内联汇编的DLL库。在function test_MMX中,我想添加两个特定长度的数组 extern“C”\uuuuuu declspec(dllexport)void\uuuu stdcall test\ummx(int*第一个数组,int*第二个数组,int长度) { __asm { mov-ecx,长度; mov esi,第一个_阵列; shr-ecx,1; mov edi,第二个_阵列; 标签: movq-mm0,QWORD-PTR[esi]; paddmm0,qwordptr[edi]; 增加edi,8; movq-QWORD-PTR[esi],mm0; 添加esi,8; dec-ecx; jnz标签; } }

C# 警告C4799:功能没有EMMS指令 我试图创建C++应用程序,它使用了包含C++代码和内联汇编的DLL库。在function test_MMX中,我想添加两个特定长度的数组 extern“C”\uuuuuu declspec(dllexport)void\uuuu stdcall test\ummx(int*第一个数组,int*第二个数组,int长度) { __asm { mov-ecx,长度; mov esi,第一个_阵列; shr-ecx,1; mov edi,第二个_阵列; 标签: movq-mm0,QWORD-PTR[esi]; paddmm0,qwordptr[edi]; 增加edi,8; movq-QWORD-PTR[esi],mm0; 添加esi,8; dec-ecx; jnz标签; } },c#,assembly,warnings,inline-assembly,mmx,C#,Assembly,Warnings,Inline Assembly,Mmx,运行应用程序后,会显示以下警告: 警告C4799:“测试MMX”功能没有EMMS指令 当我想测量运行此函数的时间C(以毫秒为单位)时,它返回此值:-922337203685477,而不是(例如00141) 有什么办法解决它吗 由于MMX别名在浮点寄存器上,任何使用MMX指令的例程都必须以EMMS指令结束。(X86的任何C或C++调用约定都是安全的) 编译器警告您,您编写的例程使用MMX指令,但不会以EMMS指令结束。这是一个等待发生的错误,只要一些FPU指令试图执行 这是MMX的一个巨大缺点,也

运行应用程序后,会显示以下警告:

警告C4799:“测试MMX”功能没有EMMS指令

当我想测量运行此函数的时间C(以毫秒为单位)时,它返回此值:
-922337203685477
,而不是(例如
00141


有什么办法解决它吗

由于MMX别名在浮点寄存器上,任何使用MMX指令的例程都必须以
EMMS
指令结束。(X86的任何C或C++调用约定都是安全的) 编译器警告您,您编写的例程使用MMX指令,但不会以
EMMS
指令结束。这是一个等待发生的错误,只要一些FPU指令试图执行

这是MMX的一个巨大缺点,也是您不能自由混合MMX和浮点指令的原因。当然,您可以直接抛出
emm
指令,但这是一条缓慢、高延迟的指令,因此会降低性能。SSE在这方面与MMX具有相同的限制,至少对于整数运算是如此。SSE2是解决这个问题的第一个指令集,因为它使用自己的离散寄存器集。它的寄存器宽度也是MMX的两倍,所以一次可以做更多的事情。由于SSE2完成了MMX所做的一切,但速度更快、更简单、效率更高,并且受到奔腾4及更高版本的支持,因此现在很少有人需要编写使用MMX的新代码。如果你能使用SSE2,你应该。它将比MMX快。不使用MMX的另一个原因是它在64位模式下不受支持

无论如何,编写MMX代码的正确方法是:

__asm
{
     mov   ecx, [length]
     mov   eax, [first_array]
     shr   ecx, 1
     mov   edx, [second_array]
 label:
     movq  mm0, QWORD PTR [eax]
     paddd mm0, QWORD PTR [edx]
     add   edx, 8
     movq  QWORD PTR [eax], mm0
     add   eax, 8
     dec   ecx
     jnz   label
     emms
 }
请注意,除了
EMMS
指令(当然,该指令位于循环之外),我还做了一些额外的更改:

  • 汇编语言指令不以分号结尾。事实上,在汇编语言的语法中,分号用于开始注释。所以我删除了你的分号
  • 我还为可读性添加了空格
  • 而且,虽然这并不是绝对必要的(微软的内联汇编程序已经足够宽容了,可以让你不去做),但最好是明确地将地址(C/C++变量)的使用放在方括号中,因为你实际上是在取消引用它们
  • 正如一位评论员所指出的,您可以在内联汇编中自由使用
    ESI
    EDI
    寄存器,因为内联汇编程序将检测它们的使用,并生成相应的附加指令来推送/弹出它们。事实上,它将对所有非易失性寄存器执行此操作。如果你需要额外的寄存器,那么你需要它们,这是一个很好的特性。但是在这段代码中,您只使用了三个通用寄存器,并且在调用约定中,有三个通用寄存器被专门定义为volatile(即,可以被任何函数自由关闭):
    EAX
    EDX
    ECX
    。所以你应该使用这些寄存器来获得最高速度。因此,我已将您对
    ESI
    的使用更改为
    EAX
    ,并将您对
    EDI
    的使用更改为
    EDX
    。这将改进您看不到的代码,即编译器自动生成的序言和尾声
不过,这里潜伏着一个潜在的速度陷阱,那就是对齐。为了获得最大速度,MMX指令需要对在8字节边界上对齐的数据进行操作。在一个循环中,未对齐的数据对性能有着复杂的负面影响:不仅数据在第一次通过循环时未对齐,造成了显著的性能损失,而且保证在随后的每次通过循环时都未对齐。因此,要使此代码有任何快速的机会,调用方需要保证
第一个数组
第二个数组
在8字节边界上对齐

如果您不能保证这一点,那么该函数实际上应该添加额外的代码来修复失调。基本上,在开始循环之前,您希望在开始时执行两个非向量操作(对单个字节),直到达到合适的对齐。然后,您可以开始发出矢量化MMX指令

(在现代处理器上,未对齐的负载不再受到惩罚,但如果您以现代处理器为目标,您将编写SSE2代码。在需要运行MMX代码的旧处理器上,对齐将是一件大事,未对齐的数据将破坏您的性能。)

现在,这个内联程序集不会产生特别高效的代码。使用内联汇编时,编译器始终为函数生成序言和尾声代码。这并不可怕,因为它在关键的内部循环之外,但它仍然是你不需要的粗糙。更糟糕的是,内联汇编块中的跳转往往会混淆MSVC的内联汇编程序,并导致它生成次优代码。它过于谨慎,防止您做一些可能损坏堆栈或导致其他外部副作用的事情,这很好,但您编写内联汇编的全部原因(可能)是因为您需要maxi
__asm
{
     mov   ecx, [length]
     mov   eax, [first_array]
     shr   ecx, 1
     mov   edx, [second_array]
 label:
     movq  mm0, QWORD PTR [eax]
     paddd mm0, QWORD PTR [edx]
     add   edx, 8
     movq  QWORD PTR [eax], mm0
     add   eax, 8
     dec   ecx
     jnz   label
     emms
 }
#include <intrin.h>   // include header with MMX intrinsics


void __stdcall Function_With_Intrinsics(int *first_array, int *second_array, int length)
{
   unsigned int counter = static_cast<unsigned int>(length);
   counter /= 2;
   do
   {
      *reinterpret_cast<__m64*>(first_array) = _mm_add_pi32(*reinterpret_cast<const __m64*>(first_array),
                                                            *reinterpret_cast<const __m64*>(second_array));
      first_array  += 8;
      second_array += 8;
   } while (--counter != 0);
   _mm_empty();
}
PUBLIC  ?Function_With_Intrinsics@@YGXPAH0H@Z
; Function compile flags: /Ogtpy
_first_array$  = 8                  ; size = 4
_second_array$ = 12             ; size = 4
_length$       = 16             ; size = 4
?Function_With_Intrinsics@@YGXPAH0H@Z PROC
    mov    ecx, DWORD PTR _length$[esp-4]
    mov    edx, DWORD PTR _second_array$[esp-4]
    mov    eax, DWORD PTR _first_array$[esp-4]
    shr    ecx, 1
    sub    edx, eax
$LL3:
    movq   mm0, MMWORD PTR [eax]
    movq   mm1, MMWORD PTR [edx+eax]
    paddd  mm0, mm1
    movq   MMWORD PTR [eax], mm0
    add    eax, 32
    dec    ecx
    jne    SHORT $LL3
    emms
    ret    12
?Function_With_Intrinsics@@YGXPAH0H@Z ENDP
#include <intrin.h>   // include header with SSE2 intrinsics


void __stdcall Function_With_Intrinsics_SSE2(int *first_array, int *second_array, int length)
{
   unsigned int counter = static_cast<unsigned>(length);
   counter /= 4;
   do
   {
      _mm_storeu_si128(reinterpret_cast<__m128i*>(first_array),
                       _mm_add_epi32(_mm_loadu_si128(reinterpret_cast<const __m128i*>(first_array)),
                                     _mm_loadu_si128(reinterpret_cast<const __m128i*>(second_array))));
      first_array  += 16;
      second_array += 16;
   } while (--counter != 0);
}
PUBLIC  ?Function_With_Intrinsics_SSE2@@YGXPAH0H@Z
; Function compile flags: /Ogtpy
_first_array$  = 8                  ; size = 4
_second_array$ = 12             ; size = 4
_length$       = 16             ; size = 4
?Function_With_Intrinsics_SSE2@@YGXPAH0H@Z PROC
    mov     ecx, DWORD PTR _length$[esp-4]
    mov     edx, DWORD PTR _second_array$[esp-4]
    mov     eax, DWORD PTR _first_array$[esp-4]
    shr     ecx, 2
    sub     edx, eax
$LL3:
    movdqu  xmm0, XMMWORD PTR [eax]
    movdqu  xmm1, XMMWORD PTR [edx+eax]
    paddd   xmm0, xmm1
    movdqu  XMMWORD PTR [eax], xmm0
    add     eax, 64
    dec     ecx
    jne     SHORT $LL3
    ret     12
?Function_With_Intrinsics_SSE2@@YGXPAH0H@Z ENDP
void Naive_PackedAdd(int *first_array, int *second_array, int length)
{
   for (unsigned int i = 0; i < static_cast<unsigned int>(length); ++i)
   {
      first_array[i] += second_array[i];
   }
}