用Visual C++优化代码2010-04-16Mark Lacey摘要:本文介绍了Visual C++ .NET 2003产品中提供的代码优化功能。此外,对于那些还不熟悉 Visual C++ .NET 2002 中进行的改进的读者,本文还用一个较短的部分介绍了在这一改进中引入的新的“全程序优化”功能。最后,本文讨论了一些与优化有关的“最佳策略”,以及对 Visual C++ 编译器进行的一般性增强。简介虽然得到了一种新的工具,但对于自己是否以可能的最佳方式使用它没有把握,这总是一件令人感到沮丧的事情。该白皮书试图减少您对 Visual C++ 优化器可能具有的忧虑,从而使您确信自己正在最大限度地发挥它的作用。Visual C++ .NET 2003Visual C++ .NET 2003 版本增加了两个新的与性能有关的编译器选项,此外还包含了对 Visual C++ .NET 2002 中附带的几项优化的改进。第一个新的与性能有关的选项是 /G7。该选项告诉编译器针对 Intel Pentium 4 和 AMD Athlon 处理器优化代码。用 /G7 编译应用程序获得的性能改进各不相同,但与 Visual C++ .NET 2002 所生成的代码相比较,典型程序的执行时间减少 5% 到 10% 并不罕见,对于包含大量浮点代码的程序而言,甚至可能减少 10% 到 15%。改进的范围可能相差很大,在某些情况下,用户如果用 /G7 编译并且在最新一代处理器上运行,则会看到改进的幅度超过 20%。使用 /G7 并不意味着编译器将产生只能在 Intel Pentium 4 和 AMD Athlon 处理器上运行的代码。用 /G7 编译的代码仍然能够在这些处理器的旧代产品中运行,尽管可能有一些小的性能损失。此外,我们已经注意到一些特殊的情况,即用 /G7 编译时产生了在 AMD Athlon 上运行速度更慢的代码。当未指定 /Gx 选项时,编译器将默认使用 /GB,即“混合”优化模式。在 Visual C++ .NET 的 2002 版本和 2003 版本中,/GB 都等价于 /G6,也就是针对 Intel Pentium Pro、Pentium II 和 Pentium III 优化代码。在使用 /G7 时进行的改进的一个示例是,在执行以常数为乘数的整数乘法时,更好地为 Intel Pentium 4 选择指令。例如,以下面的代码为例:
int i;
...
// Do something that assigns a value to i.
...
return i*15;
当用 /G6(默认选项)编译时,我们将产生以下指令:
mov eax, DWORD PTR _i$[esp-4]
imul eax, 15
当用 /G7 编译时,我们产生了更快(但更长)的指令序列,避免了使用 imul 指令,该指令在 Intel Pentium 4 上具有 14 个周期的滞后时间。
mov ecx, DWORD PTR _i$[esp-4]
mov eax, ecx
shl eax, 4
sub eax, ecx
第二个与性能相关的选项是 /arch:[参数],它采用参数 SSE 或 SSE2。该选项使编译器可以利用 Streaming SIMD Extensions (SSE) 和 Streaming SIMD Extensions 2 (SSE2) 指令,以及其他在支持 SSE 和/或 SSE2 的处理器上提供的新指令。当用 /arch:SSE 编译时,产生的代码将只能在支持 SSE 指令以及 CMOV、FCOMI、FCOMIP、FUCOMI 和 FUCOMIP 的处理器上运行。与此类似,当用 /arch:SSE2 编译时,产生的代码将只能在支持 SSE2 指令的处理器上运行。对于 /G7 而言,用 /arch:SSE 或 /arch:SSE2 编译应用程序所获得的性能改进是不同的。通常的改进是执行时间减少 2% 到 3%,尽管在某些罕见的情况下测量到执行时间减少了5% 以上。/arch:SSE 选项具有以下特定效果:
• | 为单精度浮点 (float) 变量利用 SSE 指令 — 如果这样能获得性能改进。 |
• | 利用 CMOV 指令 — 该指令最初是在 Intel Pentium Pro 处理器中引入的。 |
• | 利用 FCOMI、FCOMIP、FUCOMI 和 FUCOMIP 指令 — 它们最初也是在 Pentium Pro 处理器中引入的。 |
/arch:SSE2 选项具有 /arch:SSE 选项的所有效果,并且还具有以下效果:
• | 为双精度浮点 (float) 变量利用 SSE 指令 — 如果这样能获得性能改进。 |
• | 为 64 位移位利用 SSE2 指令。 |
除了上述好处以外,在将 /GL(“全程序优化”)选项与 /arch:SSE 或 /arch:SSE2 结合使用来进行生成时,编译器将为具有浮点参数和返回值的函数使用自定义调用约定。最后,在 Visual C++ .NET 2003 中,对在该产品的以前版本中引入的几项优化进行了增强。其中一项增强是能够消除传递“死”参数(那些未在被调用的函数中引用的参数)的情况。例如:
int
f1(int i, int j, int k)
{
return i+k;
}
int
main()
{
int n = a+b+c+d;
m = f1(3,n,4);
return 0;
}
在 function f1() 中,第二个参数从未使用。当用 /GL(“全程序优化”)选项编译时,编译器将为 main() 中对 f1() 的调用产生与下面类似的指令序列:
mov eax, 4
mov ecx, 3
call ?f1@@YAHHHH@Z
mov DWORD PTR ?m@@3HA, eax
在该示例中,永远不会执行对“n”的值的计算,并且只有在 f1() 中引用的两个参数被传递给 f1()(而且它们是在寄存器中传递,而不是在堆栈上传递)。同时,该示例是在禁用内联功能的情况下编译的,因为如果启用内联功能,则该调用将被完全优化掉,而剩余的代码会将“m”设置为值 7。