执行存储在数据段中的x86指令会导致性能下降?

时间:2009-10-21 23:10:24

标签: performance caching x86 alignment execution

我有一个简单的程序,首先将一些本机x86指令写入声明的缓冲区,然后设置一个指向此缓冲区的函数指针并进行调用。但是,当在堆栈上分配缓冲区时(而不是在堆上,甚至在全局数据区中),我注意到严重的性能损失。我验证了数据缓冲区中指令序列的开始是在16字节边界上(我假设这是cpu需要(或想要)的内容)。我不知道为什么它会在我执行指令的过程中产生影响,但在下面的程序中,“GOOD”在我的双核工作站上执行4秒钟,而“BAD”需要6分钟左右。是否存在某种对齐/ i-cache /预测问题?我对VTune的评估许可刚刚结束,所以我甚至无法对此进行分析:(。谢谢。


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef int (*funcPtrType)(int, int);

int foo(int a, int b) { return a + b; }

void main()
{
  // Instructions in buf are identical to what the compiler generated for "foo".
  char buf[201] = {0x55,
                   0x8b, 0xec,
                   0x8b, 0x45, 0x08,
                   0x03, 0x45, 0x0c,
                   0x5D,
                   0xc3
                  };

  int i;

  funcPtrType ptr;

#ifdef GOOD
  char* heapBuf = (char*)malloc(200);
  printf("Addr of heap buf: %x\n", &heapBuf[0]);
  memcpy(heapBuf, buf, 200);
  ptr = (funcPtrType)(&heapBuf[0]);
#else // BAD
  printf("Addr of local buf: %x\n", &buf[0]);
  ptr = (funcPtrType)(&buf[0]);
#endif

  for (i=0; i < 1000000000; i++)
    ptr(1,2);
}

运行此结果的结果是:

$ cl -DGOOD ne3.cpp
Microsoft(R)32位C / C ++优化编译器版本11.00.7022适用于80x86
版权所有(C)Microsoft Corp 1984-1997。版权所有。

ne3.cpp
Microsoft(R)32位增量链接器版本5.10.7303
版权所有(C)Microsoft Corp 1992-1997。版权所有。

/out:ne3.exe
ne3.obj
$ time ./ne3
堆buf的地址:410eb0

真正的0m 4.33s
用户0m 4.31s
sys 0m 0.01s
$
$
$ cl ne3.cpp
Microsoft(R)32位C / C ++优化编译器版本11.00.7022适用于80x86
版权所有(C)Microsoft Corp 1984-1997。版权所有。

ne3.cpp
Microsoft(R)32位增量链接器版本5.10.7303
版权所有(C)Microsoft Corp 1992-1997。版权所有。

/out:ne3.exe
ne3.obj
$ time ./ne3
本地buf的地址:12feb0

真正的6m41.19s
用户6m40.46s
sys 0m 0.03s
$

感谢。

  • Shasank

2 个答案:

答案 0 :(得分:3)

安全堆栈保护?

粗略猜测,您可能会遇到基于MMU的堆栈保护方案。许多安全漏洞基于故意的缓冲区溢出,将可执行代码注入堆栈。解决这些问题的一种方法是使用不可执行的堆栈。这会导致进入操作系统的陷阱,我认为操作系统或某些病毒SW可能会有所作为。

负i-cache一致性交互?

另一种可能性是使用对附近地址的代码和数据访问都会破坏CPU缓存策略。我相信x86实现了一个基本上自动的代码/数据一致性模型,这可能导致在任何内存写入上大量附近的缓存指令失效。你不能通过改变你的程序来不使用堆栈(显然你可以移动动态代码)来解决这个问题,因为堆栈是由机器代码一直编写的,例如,每当推送参数或返回地址时程序调用。

现在CPU相对于DRAM甚至是外层缓存环都非常快,所以任何破坏内部缓存环的东西都会非常严重,而且它的实现可能涉及CPU实现中的某种微陷阱然后在HW中使用“循环”来使事情无效。英特尔或AMD不会担心速度问题,因为对于大多数程序来说,它永远不会发生,而且一旦发生,它通常只会在加载程序后发生一次。

答案 1 :(得分:2)

我的猜测是,由于您在堆栈中也有变量i,当您在i循环中更改for时,您会丢弃与代码相同的缓存行坐在。将代码放在缓冲区中间某处(也许放大缓冲区),以便将其与其他堆栈变量分开。

另请注意,堆栈上的指令执行通常是被利用的安全漏洞(例如缓冲区溢出)的标志。

因此,OS通常配置为禁止此行为。病毒扫描程序也可以对其采取措施。也许你的程序每次尝试访问该堆栈页面时都会运行安全检查(虽然在这种情况下我希望sys时间字段更大。)

如果您想“正式”使内存页面可执行,您应该查看VirtualProtect()