热门文章 | 热门软件| 热门源码 | 热门电影 | 知识库 | 联系我们
软件 源码 教程 影视 健康 招聘
  HTML | JavaScript | ASP | PHP | JSP | NET | VB | VC | VF | Windows | Linux | Mysql | Mssql | Oracle | Struts 
当前位置: 创世纪计算机资源网 -> 文章频道 ->vc 
站内搜索:
用Visual C++ 2005编写更快的代码(3)
作者:佚名 来源:不详 整理日期:2007-4-15

优化

    所有优秀的软件开发人员都想要确保他们的软件能够正常运行。编译器编写人员是一种特殊的开发人员;它们的代码不仅需要正常运行,而且他们的代码生成的代码必须尽可能地具有高的效率。由于这个原因,任何成功的编译器的背后都要有一个好的优化支持。在这方面,Visual C++ 2005是无可挑剔的。

    由于Visual Studio .NET 2002和Visual Studio .NET 2003在本机代码的性能提高方面做了许多的工作,所以它们加入了对C++编译器的一些惊人的优化。在加入了SSE和SSE2体系结构的同时,它们还提供了针对Intel Pentium IV的支持。最为显著的是加入了全局程序优化(Whole Program Optimization,WPO),它允许链接器在将每个经过编译的。cpp文件变成。obj文件时对整个程序进行优化。这些对象文件和普通的对象文件有所不同,因为它们包含的不是本机代码,而是用来在编译器前端和后端进行通信的中间语言。然后,链接器就能将所有这些文件优化成一个大的单元,从而提供更多的内联机会、更好的堆栈对齐方式和在各种情况下使用自定义调用约定的可能性。Visual C++ 2005使用称为自顶向下、自底向上分析这样的新功能来改进(WPO)。但是大的改进是以特性引导最优化(Profile Guided Optimization,POGO)的形式出现的,在编译器中提供的这种全新的功能将会对性能有所改进。

    就编译器而言,对源代码的静态分析将会留下许多悬而未决的问题。如果在一个if语句中比较两个变量,第一个变量比第二个变量大的几率是多少?在一个switch语句中,哪一个case被选中的次数最多?哪些函数最常使用,而哪些代码常常被忽略?如果编译器在编译时知道代码在运行时应该如何使用的话,它就可以为大多数情况进行优化。这正是Visual C++ 2005编译器所能够做到的。

    POGO第一步需要编译代码并将其链接成一个规范构建,它具备一组分析探测器。当使用WPO时,由编译器生成并导入到链接器的对象文件是由中间语言而不是本机代码构成的。这些探测器分为两种:值探测器和计数探测器。值探测器用来构造变量存放的值的直方图,而计数探测器用来跟踪您通过该应用程序往返某一特定路径的次数。当应用程序运行并正常使用时,数据是从所有这些探测器中集合汇集而成的,并被写入一个配置文件数据库。这些配置文件数据和原始的。obj文件一起被导入到链接器。链接器能够分析配置文件数据,确定应该应用的其他优化,并生成一个新的非规范应用程序构建。这只是一个经过编译的版本,而不是一个可以用来发布给客户的规范版本。

    特性引导最优化(POGO)支持各种各样的优化。以计数探测器为基础,可以在每个函数调用地点做出内联决策。通过使用值探测器,可以重新排列switch和if-else构造,这样就可以提取出最常用的值,并且避免在找到常见的case之前进行额外的不必要检查。还可以重新排列代码段,从而能够将最常用的路径排在一起,而不是强制要求在代码内进行不必要的跳转。这避免了开销很高的转换检测缓冲器(Translation Lookaside Buffer,TLB)颠簸和页面调度(paging)。

    可以将不常使用的代码放在该模块的一个特定的部分中,这也有助于避免上述问题。可以执行虚拟调用的途径,因为虚拟调用地点常常导致对某一特定的类型进行调用,从而能够避免常见情况中的虚表查找。可以执行局部内联,借此确保只对函数中经常使用的代码段进行内联,而且这种决策是基于每个调用地点做出的。此外,某些代码段会以某种优化为目的进行编译,而其他代码段的编译则有着不同的目标。例如,经常使用的和/或小的函数可以编译为最大化速度(/O2);而不经常使用和/或大一些的函数会被编译为最小化空间(/O1)。

    如果能够了解实际情况,并且能够将应用程序投入经常使用的情况,而与此同时还进行一些规范化的工作,则应用程序的性能将会得到极大的提高。最近,使用POGO对SQL Server进行了重新编译,并且在许多常见的情况下,获得了30%的性能飙升。这样下去,可以断定,Microsoft会开始使用这一技术来将它的许多产品进行编译。需要注意的是,在分析规范构建时,不要试图涵盖全部的代码,这一点非常重要。POGO的全部意义在于确定如何优化常见的用例。如果试图涵盖全部的代码,将得到深刻的教训。

    Visual C++ 2005还增加了对OpenMP的支持,它是一个构建多线程程序的开放规范。它由一组程序组成,用来指示编译器代码的哪些部分可以平行放置。如果代码具有大的循环并且这种循环与前面的迭代没有依赖关系,则这种代码最适合使用OpenMP.看一看下面这个简单的copy函数,它将数组a和b中的值相加,并将结果存储在c中:

void copy(int a[], int b[], int c[], int length)
{
    #pragma omp parallel
    for(int i=0; i<length; i++)
    {
        c[i] = a[i] + b[i];
    }
}

    在拥有多个处理器的机器上,编译器会生成多线程来执行这个循环的迭代,而每个线程将执行复制操作的一部分。需要注意的是,编译器无法验证循环是否具有依赖性,因此它不会阻止在不适当的情况下使用这些代码。如果具有依赖性,就极有可能得到与想像的不同的错误结果,尽管它们在规范方面是正确的。

    虽然使用OpenMP的最大好处常常来源于平行放置循环(如刚才的例子所示),但是在直线型代码中使用它也会使性能得到改善。“#pragma omp section”指令可以用来区分一段代码中的非依赖性部分,这样就可以让开发人员指定可以并行运行的区域。然后,编译器就可以产生多线程,从而在不同的处理器上执行这些代码段。

    对于使用。NET的开发人员来说,一个重要的改变是,Visual C++ 2005优化器对中间代码做出的优化和对本机平台做出的优化大体上是一样的,尽管优化器是通过不同的调优来做到这一点的。而现在的JIT编译器是在运行时分析并优化的,它允许C++编译器在初次编译时就进行优化,这样也可以提供极大的性能优势(C++编译器就可以有更多的时间来进行分析而不是保持JIT)。Visual C++ 2005编译器首次对托管类型进行了优化:执行循环优化、表达式优化和内联。但是在有些地方编译器是无法对基于。NET的代码进行优化的。例如,由于指针算术运算的无法验证性,它就有一些无能为力了,而且某些代码由于CLR对类型和成员可访问性的严格要求而无法内联,尽管编译器的确对合法的内联机会进行了大量的分析。此外,优化中间代码需要平衡考虑对JIT编译器的影响。例如,不可能去解开一个循环而暴露过多的变量给JIT编译器,因此,JIT编译器必须进行注册表分配(一个NP完成问题)。Visual C++小组正在对这些问题进行研究,并在系统发布时会得到一个经过良好调优的优化解决方案。

    安全性

    在2002年,Bill Gates发出了可信任计算(Trustworthy Computing)倡议,这对Microsoft开发的所有产品都有着不可小视的冲击力。Windows操作系统的开发人员在安全性培训和代码评审上花费了几个月的时间,这使得Windows Server 2003成为该公司曾经发布过的安全性最高的一个操作系统。Microsoft Office 2003还包含了许多安全功能,例如Information Rights Management (IRM)、更好的宏安全性和在Outlook中阻止HTML下载等等。而编译器小组也在大踏步地使他们开发的编译器及其生成的代码变得更安全。Visual Studio .NET 2002引入了缓冲区安全检查/GS编译器选项。这一标志将导致编译器在决定为有缓冲区溢出攻击嫌疑的函数返回地址之前就预先在堆栈上分配空间。在函数进入时,一个带有已知计算值的安全Cookie会被放在这个缓冲区中,而在函数退出时,编译器会对其进行检查,以确保该Cookie没有被破坏。对Cookie值的更改意味着有重写返回地址的可能性,而这会产生一个错误并导致应用程序终止。当然,这并不能防止所有的缓冲区溢出攻击。Visual Studio .NET 2003还增强了/GS功能。它通过对堆栈上的局部变量进行排序来将数组分配在高于其余局部变量的内存地址上,从而防止这些局部变量造成溢出。这样会阻止基于vtable入侵的攻击和其他基于指针的攻击。

    Visual C++ 2005对这一强大的功能进行了又一次升级。当进行函数调用时,函数的激活记录按照如图7所示的方式放置。如果一个局部缓冲区发生了溢出,黑客就有可能重写其堆栈上的所有内容,其中包括异常处理函数记录、安全cookie、帧指针、返回地址,以及函数的参数。这些值中的大多数都是通过各种机制保护的(例如安全异常处理),然而,在一个以函数指针作为参数的函数中,利用缓冲区溢出仍有可能。如果一个函数将函数指针(或者一个包含函数指针的结构或类)作为参数,黑客就可能会重写这一指针的值,并使代码执行任何他想要运行的函数。为了防止这一点,Visual C++ 2005编译器会分析所有的函数参数来防止这一漏洞。那些易受攻击的参数会在堆栈的局部变量下创建副本,然后使用这些副本而不是参数本身。缓冲区溢出可能会重写原始值,但是它们不会被利用,因为正在使用的副本仍然保持原始值。

    与“缺省安全(secure by default)”可信任计算指令相一致的是,Visual C++ 2005编译器现在默认支持缓冲区安全检查选项。这会有助于使所有用Visual C++编译的产品更加安全。实际上,Microsoft现在正在用这个已经启用的选项来构建它的所有产品,其中包括Windows、Office和SQL Server.Visual C++ 2005在其他方面的进步就是确保代码是以考虑安全性为前提开发的。绝大多数C++应用程序都依赖于C运行时库(CRT)和标准模板库(STL)。这些库在最初设计时,代码安全性不具有高的优先级,而许多现在普遍使用的攻击也不存在。这样做的结果是,许多由这些库提供的功能常常以一种不安全的方式被使用,从而使得应用程序暴露在一些潜在的攻击之下。最近出版的书籍(如Michael Howard撰写的Writing Secure Code(Microsoft Press,2002年)中,都强调了某些编码实践的重要性,这些实践都不以这些库为例。在Visual C++ 2005中,Microsoft推出了经过重新编写的这些库的新版本,它致力于找出所有可能导致一般安全问题的函数,并提供一些更安全的替换版本。这项工作的长远目标是弃用所有“不安全”的版本,而提倡使用一些具有相同功能但更加健壮的版本。单单是CRT的这个新版本,就引入了超过400个新的“安全”函数,从而可以确保检查所有的指针参数是否为空值,并且所有进行内存复制操作的函数除了知道目的缓冲区和源缓冲区之外,还要知道需要复制多少字节的数据。

    小结

     Visual C++ 2005的新功能还有很多,难以在此一一详述:混合映像的延迟CLR加载、本机应用程序域 API、为在应用程序域和进程方面提供更好的全局变量支持而引入的新的声明规范、模块构造函数、为对象文件和。NET模块提供的链接器支持、隐式装箱、使用与C#开发人员所喜爱的语法一样的XML注释、全新的面向。NET框架的STL版本、参数数组、别名提示、新的浮点模型、运算符重载等等。

    任何基于。NET框架的语言的新版本经常让人们想问“如果们的开发小组想要编写一个面向。NET的应用程序,应该使用哪一种语言?”现在,如果您正在做许多本机互操作工作,那么很简单。C++是开发本机互操作最易于使用的语言,而且它常常具有最佳的性能。此外,如果您想要将现有的C++应用程序转到。NET上,那就的确没有更好的选择了。实际上,当您把现有的应用程序转化为。NET框架时,使用Visual C++是Microsoft极力推荐的一条途径。

    至于新的应用程序,您或许会问,为什么不熟悉。NET的开发人员选择一种语言而不选择另一种语言。由于每种语言都有其优势,所以无法对这个问题作出简单的回答,但对于纯基于。NET的应用程序而言,C#、Visual Basic和C++中的体验基本相同。如果您作为一个开发人员已经习惯于使用某种特定的语言,就没有什么重要的原因要转向使用另外一种语言了。

    但如果您正在开发某种互操作,您也许会选择C++语言而不是其他。使用C++体验肯定要好于其他的语言,因为在C++中直接内置了范围广泛的互操作支持。此外,它通过析构函数提供的确定性清除功能在消除资源泄漏和确保应用程序的正确性时简直就是无价之宝。C++还有许多强大的功能可以与CLR提供的功能联合使用。例如,C++不仅支持模板和泛型,而且还支持它们的组合。这比单独使用其中任何一个功能更富有表现力,也更为强大。尤其有用的一个库编写技术是编写实现一般接口的模板。这会为您的模板提供所有的灵活性和强大的功能(例如专用化),而它仍会让其他语言有通过一般接口直接使用从模板实例化的对象的能力。总而言之,C++的确找到了属于自己的位置。

 

[1]  [2]  [3]  
相关文章