Viusal C++ 的优化代码
Mark Lacey
Microsoft Corporation
April 2003
翻译: cnss
概要:这篇文章介绍了 Visual C++.NET 2003 中的代码优化。另外,有些读者可能对 VC.NET 2002 的优化不太了解,所以我们会简短介绍一下全程优化 (Whole Program Optimization) 。最后我们用一些例子充分表现一下 VC.NET 的优化性能,并对其讨论。
本文适用于: Visual C++ .NET 2003
-------------------------------------------------------------------
** 前言 ** ** **
人们在使用一个新的编程工具时总会感到缺乏自信,本文试图让你对 VC 的代码优化有更直观的感觉,希望你能通过阅读本文从 VC 中 " 得到 " 更多的东西。
** Visual C++ .NET 2003 **
VC.NET 2003 不仅带来了两个新的优化选项,它还改进了 VC.NET 2002 中一些优化的性能。
第一个新增选项是 "/G7" ,它告诉编译器对 Intel Pentium 4 和 AMD Athlon 处理器进行优化。
使用 "/G7" 选项编译的程序,当我们和 VC.NET 2002 生成的代码比较时发现,它通常能使 典型的程序 的运行速度提高 5 到 10 个百分点,如果使用了大量浮点代码甚至能提高 10 到 15 个百分点。而提高的优化程度可能很高也可能较低,在一些使用最新 CPU 和 "/G7" 选项的测试中,甚至提高了 20% 的性能。
使用 "/G7" 选项 不代表 生成的代码只能运行在 Intel Pentium 4 和 AMD Athlon 处理器上。这些代码仍可以运行在老的 CPU 上,只是在性能表现上可能有 " 小小的惩罚 " 。另外,我们观察到一些程序使用 "/G7" 后在 AMD Athlon 上运行的比用 Intel Pentium 4 更慢。
当没使用 "/Gx" 选项时,编译器会默认使用 "/GB" 选项,此时为 "blended" 优化模式。在 VC.NET 2002 和 VC.NET 2003 中, "/GB" 代表 "/G6" ,即为 Intel Pentium Pro , Pentium II , Pentium III 处理器优化。
这儿有一个例子,它展示了做 与常整数乘法 时使用 Pentium 4 和 "/G7" 的优化效果,下面是源代码:
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( 乘 ) 指令,在 Pentium 4 上执行只需要 14 个周期。目标代码如下:
mov ecx, DWORD PTR _i$[esp-4]
mov eax, ecx
shl eax, 4
sub eax, ecx
第二个优化选项是 "/arch:[argument]" ,用它可对 SSE 或 SSE2 优化,生成使用 Streaming SIMD Extensions (SSE) 和 Streaming SIMD Extensions 2 (SSE2) 指令集的程序。当使用 "/arch:SSE" 选项时,目标代码 只能 运行在支持 SSE 指令 ( 如: CMOV , FCOMI , FCOMIP , FUCOMI , FUCOMIP) 的 CPU 上。当使用 "/arch:SSE2" 选项时,目标代码 只能 运行在支持 SSE2 指令集的 CPU 上。
相比于 "/G7" ,使用了 SSE 或 SSE2 优化的程序,一般能减少 2-3% 的运行时间,个别测试中甚至能减少 5% 的运行时间。
使用 "/arch:SSE" 可得到以下效果:
1 。在使用 单精度浮点数 时,使用 SSE 指令对其处理。
2 。使用 CMOV 指令,它最早被 Pentium Pro 支持。
3 。使用 FCOMI , FCOMIP , FUCOMI , FUCOMIP 指令,它们也是最早被 Pentium Pro 支持的。
使用 "/arch:SSE2" 的话,可以得到所有 "/arch:SSE" 选项的效果,另外还有以下几个效果:
1 。在使用 双精度浮点数 时,使用 SSE2 指令对其处理。
2 。使 SSE2 指令集做 64 位切换。 ( 原文: Making use of SSE2 instructions for 64-bit shifts)
还有其它的好处,在同时使用 "/arch:SSE" 或 "/arch:SSE2” 和 "/GL"( 全程优化 ) 选项选项时,编译器会对浮点参数和浮点返回值做 函数调用规则 优化。
上面说的几点优化特性已经包括于 VC.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;
}
在函数 f1() 中,第二个参数从没被使用过。当我们用 "/GL"( 全程优化 ) 选项时,编译器将产生如下目标代码来调用 f1() :
mov eax, 4
mov ecx, 3
call ?f1@@YAHHHH@Z
mov DWORD PTR ?m@@3HA, eax
在这个例子里,变量 "n" 从没被运算,只有两个参数被 f1() 使用,所以只传递那两个参数 ( 并且它们是从寄存器传过去的,这比使用栈传更快 ) 。另外,编译这个例子时要禁止内联 (inlining) ,否则函数 f1() 就不存在了,而直接给 m 赋予值 7 。
** Visual C++ .NET 2002 **
VC.NET 2002 引入了全程优化 (Whole Program Optimization ,缩写为 WPO) 的概念, "/GL" 选项代表使用全程优化。全程优化意味着:编译器在 .obj 文件中存放的是 代码的中间表达 而不是目标代码,在连接时连接器对其优化处理并生成真正的目标代码。
全程优化的一个主要好处在于我们可以跨越源文件进行函数内联,这将大大提高程序的性能。还有一个好处在于编译器可以跟踪内存和寄存器的使用,以便优化使函数调用的开销更小。
下面的代表展示了全程优化的表现:
// File 1
extern void func (int *, int *);
int g, h;
int
main()
{
int i = 0;
int j = 1;
g = 5;
h = 6;
func(&I, &j);
g = g + i;
h = h + i;
return 0;
}
// File 2
extern int g;
extern int h;
void
func(int *pi, int *pj)
{
*pj = g;
h = *pi;
}
当 不使用 "/GL" 选项时,生成了如下代码:
sub esp, 8
lea eax, DWORD PTR _j$[esp+8]
push eax
lea ecx, DWORD PTR _i$[esp+12]
push ecx
mov DWORD PTR _i$[esp+16], 0
mov DWORD PTR _j$[esp+16], 1
<P class=MsoNormal style="BORDER-RIGHT: medium none; PADDING-RIGHT: 0cm; BORDER-TOP: medium none; PADDING-LEFT: 0cm; PADDING-BOTTOM: 0cm; MARGIN: 0cm 0cm 0pt; BORDER-LEFT: medium none; PADDING-TOP: 0cm; BORDER-BOTTOM: medium none; mso-border-shadow: yes; mso-border-alt: solid windowtext 1.0pt;