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标签; } }
运行应用程序后,会显示以下警告: 警告C4799:“测试MMX”功能没有EMMS指令 当我想测量运行此函数的时间C(以毫秒为单位)时,它返回此值: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的一个巨大缺点,也
-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
寄存器,因为内联汇编程序将检测它们的使用,并生成相应的附加指令来推送/弹出它们。事实上,它将对所有非易失性寄存器执行此操作。如果你需要额外的寄存器,那么你需要它们,这是一个很好的特性。但是在这段代码中,您只使用了三个通用寄存器,并且在调用约定中,有三个通用寄存器被专门定义为volatile(即,可以被任何函数自由关闭):EDI
、EAX
和EDX
。所以你应该使用这些寄存器来获得最高速度。因此,我已将您对ECX
的使用更改为ESI
,并将您对EAX
的使用更改为EDI
。这将改进您看不到的代码,即编译器自动生成的序言和尾声EDX
第一个数组
和第二个数组
在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];
}
}