使用 SSE/SSE2 进行优化






3.97/5 (26投票s)
2004年10月3日
3分钟阅读

147216

884
针对其中一种优化方法的初学者介绍。
目录
目标
我发布本文的目标是分享一些简单的优化方法。未来,我将尝试花一些时间编写更多文章。
引言
本文演示了英特尔的 SIMD(单指令多数据)扩展技术。通过使用像 movdqa
这样的新型英特尔指令进行优化,将比典型的指令更快地移动(复制)数据。
回顾
在我们继续之前,让我们回顾一下我们现在已有的知识。如今,或者更常见的是,我们在家庭甚至在工业领域都在使用 32 位处理器。像 eax
、ebx
.. 等通用寄存器都是 32 位的。sizeof(int)
= 4 (字节)。但并非所有寄存器都是 32 位的,有些寄存器具有更长的位长。十年前,英特尔推出了 MMX 扩展,其中有 8 个寄存器 mm0
、mm1
.. mm7
具有 64 位的长度。之后,英特尔推出了 SSE 扩展,它又有另外 8 个新的寄存器 xmm0
、xmm1
.. xmm7
具有 128 位的长度。如果您想了解更多详细信息,请访问我的 链接 部分。查找英特尔。
要求
首先问问你自己,你正在使用什么机器。它应该是 Intel P3 或更新的。您必须记住,这种优化方法是依赖于机器的,这意味着如果您的硬件不支持,您将无法看到差异。
代码
我创建的示例,我特意使其简单,使其在控制台模式下运行。不要剪切和粘贴,我更希望读者理解并自己尝试。这是示例的开始..
演示代码将让您看到这两个功能之间在达到相同目的时的差异。从这里开始,我不会解释太多,您将独自一人,请阅读代码中的注释。我确信你能够赶上。=)
等等!先准备好你的断点,坐稳了。当您进行调试时,请尝试单步执行这两个函数,您会注意到差异。
“DataTransferTypical
”将在每次循环中复制一个 int
(sizeof(int)
=4bytes),而“DataTransferOptimised
”将在每次循环中复制四个 int
(4*sizeof(int)
=16bytes)。
设置您的监视窗口.. 在您的监视窗口中,监视“piDst, 101”。然后你就会看到它是如何变化的...
附注:您需要安装处理器包才能让您的 MSVC++ 编译这段代码。请参阅“链接”部分。
int DataTransferTypical(int* piDst, int* piSrc, unsigned long SizeInBytes);
int DataTransferOptimised(int* piDst, int* piSrc, unsigned long SizeInBytes);
int main(int argc, char* argv[])
{
// var keep the start and end time. simple one. if u wish to have accurate
// one, please look for other article.
unsigned long dwTimeStart = 0;
unsigned long dwTimeEnd = 0;
// temporary variable
int *piSrc = NULL;
int *piDst = NULL;
int i = 0;
char cKey = 0;
unsigned long dwDataSizeInBytes = sizeof(int) * DATA_SIZE;
// u need to install processor pack in order to get msvc++ compile this
// code. see Link section.
piSrc = (int *)_aligned_malloc(dwDataSizeInBytes,dwDataSizeInBytes);
piDst = (int *)_aligned_malloc(dwDataSizeInBytes,dwDataSizeInBytes);
do
{
// initialise
memset(piSrc, 1, dwDataSizeInBytes);
memset(piDst, 0, dwDataSizeInBytes);
dwTimeStart = clock();
for(i = 0; i < ITERATION; i++)
DataTransferTypical(piDst, piSrc, dwDataSizeInBytes);
dwTimeEnd = clock();
printf("== Typical Transfer of %d * %d times of %d bytes data ==\nTime
Elapsed = %d msec\n\n",
ITERATION, DATA_SIZE, sizeof(int), dwTimeEnd - dwTimeStart);
// initialise
memset(piSrc, 1, dwDataSizeInBytes);
memset(piDst, 0, dwDataSizeInBytes);
dwTimeStart = clock();
for(i = 0; i < ITERATION; i++)
DataTransferOptimised(piDst, piSrc, dwDataSizeInBytes);
dwTimeEnd = clock();
printf("== Optimised Transfer of %d * %d times of %d bytes data ==\nTime
Elapsed = %d msec\n\n",
ITERATION, DATA_SIZE, sizeof(int), dwTimeEnd - dwTimeStart);
printf("Rerun? (y/n) ");
cKey = getche();
printf("\n\n");
}while(cKey == 'y');
_aligned_free(piSrc);
_aligned_free(piDst);
return 0;
}
#pragma warning(push)
#pragma warning(disable:4018 4102)
int DataTransferTypical(int* piDst, int* piSrc, unsigned long SizeInBytes)
{
unsigned long dwNumElements = SizeInBytes / sizeof(int);
for(int i = 0; i < dwNumElements; i++)
{
// i is offset.
*(piDst + i) = *(piSrc + i);
}
return 0;
}
int DataTransferOptimised(int* piDst, int* piSrc, unsigned long SizeInBytes)
{
unsigned long dwNumElements = SizeInBytes / sizeof(int);
// not really using it, just for debuging. it keeps number of looping.
// it also means number of packed data.
unsigned long dwNumPacks = dwNumElements / (128/(sizeof(int)*8));
_asm
{
// remember for cleanup
pusha;
begin:
// init counter to SizeInBytes
mov ecx,SizeInBytes;
// get destination pointer
mov edi,piDst;
// get source pointer
mov esi,piSrc;
begina:
// check if counter is 0, yes end loop.
cmp ecx,0;
jz end;
body:
// calculate offset
mov ebx,SizeInBytes;
sub ebx,ecx;
// copy source's content to 128 bits registers
movdqa xmm1,[esi+ebx];
// copy 128 bits registers to destination
movdqa [edi+ebx],xmm1;
bodya:
// we've done "1 packed == 4 * sizeof(int)" already.
sub ecx,16;
jmp begina;
end:
// cleanup
popa;
}
return 0;
}
#pragma warning(pop)
最后
这是我在 Code Project 上的第一篇文章,如果有什么不对劲的地方请多多包涵。此外,我希望我在这里上传的演示对于初学者来说足够简单。没什么花哨的。学习是乐趣,对吧? =)
链接
历史
只有当人们提出要求时,我才会更新这篇文章。示例代码将不会维护。
许可证
本文档没有明确的许可证,但可能包含文章文本或下载文件本身的使用条款。如有疑问,请通过下方的讨论区联系作者。
可以在此处找到作者可能使用的许可证列表