65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 SSE/SSE2 进行优化

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.97/5 (26投票s)

2004年10月3日

3分钟阅读

viewsIcon

147216

downloadIcon

884

针对其中一种优化方法的初学者介绍。

Sample Image - Fast_Data_Transfer_Sample.jpg

目录

目标

我发布本文的目标是分享一些简单的优化方法。未来,我将尝试花一些时间编写更多文章。

引言

本文演示了英特尔的 SIMD(单指令多数据)扩展技术。通过使用像 movdqa 这样的新型英特尔指令进行优化,将比典型的指令更快地移动(复制)数据。

回顾

在我们继续之前,让我们回顾一下我们现在已有的知识。如今,或者更常见的是,我们在家庭甚至在工业领域都在使用 32 位处理器。像 eaxebx.. 等通用寄存器都是 32 位的。sizeof(int) = 4 (字节)。但并非所有寄存器都是 32 位的,有些寄存器具有更长的位长。十年前,英特尔推出了 MMX 扩展,其中有 8 个寄存器 mm0mm1 .. mm7 具有 64 位的长度。之后,英特尔推出了 SSE 扩展,它又有另外 8 个新的寄存器 xmm0xmm1 .. 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 上的第一篇文章,如果有什么不对劲的地方请多多包涵。此外,我希望我在这里上传的演示对于初学者来说足够简单。没什么花哨的。学习是乐趣,对吧? =)

链接

历史

只有当人们提出要求时,我才会更新这篇文章。示例代码将不会维护。

许可证

本文档没有明确的许可证,但可能包含文章文本或下载文件本身的使用条款。如有疑问,请通过下方的讨论区联系作者。

可以在此处找到作者可能使用的许可证列表

© . All rights reserved.