missdeer之程序的野望

哪怕出没于深沉的夜里,也要在自己的黑眸上映上无数朵美丽的桃花,如此方能不自伤,不自悲……

Entries for the ‘CPPOOPGPXP’ Category

Windows下编译器内存分配释放性能对比(续)

  今天搞到了C++Builder2010,里面的bcc是6.21版本,相比bcc5.5,也是经过4个版本升级了,隐约记得在某个版本中,把CRT中的内存管理部分改用FastMM了,所以它的表现应该有所不同。
  经过与前一次基本相同的测试,得到结果。从该数据中可以看到,6.21版本提升了多线程情况下的小块内存操作的性能,在1024字节以下的malloc测试中,表现优于所有其他的编译器!但在1M字节以上的测试中,还不如bcc5.5版本。而且,它的new操作耗时比malloc操作耗时多很多,大约是半倍到一倍的样子。在单线程小块内存的操作中,表现可以用技惊四座来形容,直逼OpenWatcom,甚至偶尔略有超越。但是在大块内存操作中,反而都不如原来BCC5.5。
  详细测试用可执行程序与测试结果可以点击这里下载。

Windows下编译器内存分配释放性能对比

  前些天有人在群里说起测试malloc和HeapAlloc的效率,后来又在豆瓣上有人讨论起大块内存的策略问题,于是我决心再稍微仔细地测试一下各个编译器在这方面的表现。
  首先,我选取了OpenWatcom 1.8、Digital Mars 8.51、Borland 5.5、MinGW GCC 4.4.0、MSVC 2008、Intel 11.0.061一共6种编译器,除了Intel C++,其他的编译器都可以在网上下载并免费使用。
  然后写了一个非常简单的小程序,分别对1字节,1000字节,1024字节,1M字节,10M字节和50M字节进行重复的分配和释放,前三种大小重复15000000次,后三种大小重复150000次。为什么前三种的重复次数是后三种的100倍呢?因为测试过程中我发现,后三种的耗时在某些编译器中太长了,等不及了!
  接着,使用所有这些编译器编译出4种不同CRT配置的可执行程序。分别是单线程静态链接,单线程动态链接,多线程静态链接和多线程动态链接。我都是使用bjam的默认参数编译的,其中Intel编译器不能生成单线程静态链接的可执行程序,但我想影响很小,也就不再追究。
  最后就是运行每个程序,我的机器配置是Thinkpad T43,1.86GHz的P4m,1.5G内存,Windows XP SP3。每种跑10次,计平均时间,可以得到一些有趣的结果。从不同的维度观察这些数据,可以得到不同的结论。
  先比较不同编译器间的差别。从实际上看,MinGW和Intel都是使用了msvcrt,只不过默认情况下MinGW使用的是6.0版本,Intel则使用当前安装的VC的最高版本的CRT库,我的机器上是9.0,所以总的说来这三者的差别应该不大。
  在1字节的大小时,Digital Mars表现平稳而出色都在2s出头,而VC、MinGW和Intel都超过3s,OpenWatcom跟VC类似,但在单线程静态链接时就爆发了,只需要1s出头,而Borland在单线程中都只用1s多。
  1000字节的测试基本与1字节的情况相同,但Digital Mars的耗时却飚升了近1倍!
  而1024字节时,VC、MinGW和Intel的耗时又比1000字节时多了近1倍,看来这1000和1024之间有个阀值影响msvcrt使用不同的分配策略。
  在1M、10M和50M字节的测试中,三个测试的数据差别不大,但与前三者的测试数据比较可以看出,OpenWatcom和DigitalMars的表现平稳,变化不大,但使用msvcrt的三者则耗时飚升,大约是前二者的400~500倍,是1024字节的200~250倍,这个差别比较大!
  总的说来,Borland C++只有在单线程,小块内存分配时的表现还不错,这在DOS时代是好的,但进入了Windows时代,程序开发纷纷往多线程,大内存占用的方向发展时,它几乎没有进步。Open Watcom是受分配内存块大小影响最小的,几乎没有变化,而且总体上它在各个横向比较中性能是最好的。Digital Mars略次于Open Watcom,但它在单线程或多线程,静态或动态链接中几乎没有差别,估计最终使用的是同一个CRT库。VC、MinGW和Intel的表现基本相同,除了单线程静态链接小块内存分配外,其他情况都略好于Borland C++,所以可以看到Intel编译器的优化是集中在指令流的调整上,而对内存这块是忽略了。
  另外,我同样用C++中的new和delete进行了测试,从数据上看,略慢于malloc和free,最差距很小很小,平常的应用中基本可以忽略。而且使用Windows的HeapAlloc则可以看到,该API的表现与msvcrt的malloc/new接近,在1000字节和1024字节间有一个阀值。
  详细测试用可执行程序与测试结果可以点击这里下载。

初步搞定语法树

  看了两天小说,呃,又堕落了。由于已经看完了,今天就比较认真地折腾起flex和bison。其实之前已经把lex和yacc脚本写完大部分了,至少可以从控制台打印结果出来了。今天就修改一下yacc脚本,把原来打印到控制台的内容保存在内存中,到时候转储成xml格式。因为只是要显示在界面的树视图中,我想了想,也就只有函数定义值得这么显示一下,所以也就只处理了这部分。  总的说来,感觉yacc有点土,它只能接受一种输入接口,而我用flex时发现可以生成C++代码,所以要给bison用的话,仍然需要把这个C++类的接口再适配成bison可用的C接口。  这种任务果然是实践性非常强的工作,本来看过一些资料,当然,能找到的资料也基本内容一样,翻来覆去那么几句话几个例子,等到自己要做时,不时地有些迷惑,只得慢慢尝试,倒也捣鼓出来了。  在yacc中可以为每个token或type指定一个union中的某个成员,其实这个成员的指定只在规则描述段中的action中有用,就我看来各种资料、教程中说的那一堆实在是扰乱视线。对于一个C/C++程序员来说,这种用法只是万千技巧中的一种,实在没必要说得那么严肃仔细,好像不那么用就不行了似的。  再说个我觉得yacc土的地方,由于这种格式上的限制,在action中只能访问一些全局的变量、对象等,至少在思维逻辑上很不连贯,其实lex也有这个问题。要我说,比较让现代化的做法是它应该生成一个类,每组action触发时,应该调用该类中的某个回调函数或虚函数,这里形式有好几种,都可以考虑,不知道boost.spirit是不是这种形式的,也许ANTLR等其他类似的工具就是这么做的。  最后抱怨一下,Lua Reference Manual中附录的complete syntax不能直接用的,至少不能直接用于yacc,有好些地方似乎没写全。

库使用注意

  上午整了一两个小时,在wxWidgets程序中使用第三方库wxPropGrid,结果发现在VC2008中链接时有几个warning,虽然看起来刺眼,但似乎是可以正常运行的,也没有很在意。然后用MinGW编译链接,最后链接不通过,报未定义的符号,而这些符号是之前用VC2008时报warning的那几个,这就说明不是库编译得有问题,就是本身程序编译得有问题。  我先把焦点放在库上,wxPropGrid是编译成静态库的,这不但编译链接选项不同,连有个宏定义(静态库是WXMAKINGLIB_PROPGRID,动态库是WXMAKINGDLL_PROPGRID)都不同。我仔细观察了该宏定义对源代码的影响,并参考了wxScintilla的做法,发现区别很小,基本可以忽略。于是我琢磨着如果实在不行了,把wxPropGrid编译成动态库试试。正在这么打算的时候,突然想起来,这个宏定义在主程序中没定义啊!一定是这个原因!于是修改了主程序的配置,加上了这个宏定义,重新编译,发现果然有效,VC中也不报warning了,MinGW中也可以链接通过了!  其实这是个老问题了,只是平时很少遇到这种情形,一时没想起来。

代码的坏味道

  最近一直在学习Martin Fowler的《重构》,并且对照我参与的一个已经投入至少15人年,历时3年,约20万行,目前仍然在继续开发维护的项目,让我觉得触目惊心,其中的代码,到处充斥着Martin Fowler所谓的坏味道,而又困惑重重,不知道别的项目代码质量是如何的。  下面就都只是随便举一下项目中的实际情况为例,项目是用MFC开发,使用了Codejock的Xtreme Toolkit Pro界面扩展库。  重复代码。有3处计算MD5的实现,分别由3个开发人员完成,大概实在是这种实现的代码在网上太容易找到了。另外有一个特性,可以与另一个服务进行文件的上传、下载、更新、同步,而文件因为类型不同,做这些操作时某些细节有细小的差别,但实现中却是为每一类文件具体而完整都实现了一遍这些操作。  过长的函数。有的开发人员就是习惯性地写出长函数。整个项目中,圈复杂度超过100的有4个函数,超过20的不知道是几十还是上百个。  过大的类。有一个类的cpp文件,是18000行,另外有一个类的cpp文件是10000行。还有CMainFrame类的cpp文件,用Source Insight打开后,在列出函数列表的窗口中显示“Too complex to parse”。  过长的函数列表。有一个cpp文件中共9个函数实现,每个函数的参数都超过7个,而且含义晦涩,自从原创人员两年前离职后,没人敢去动那块代码。  发散式变化。前面提到的一个18000行的cpp文件,是一个视图的实现。如果要给该视图的右键菜单中新增加一个菜单项,并进行响应,需要修改不知多少个函数,记得曾经有个开发人员,花了一周时间都在为了一个新增的菜单项。添加代码没花多少时间,时间花在添加后,因此引发的问题上。  霰弹式修改。有两个模块都需要一个高亮显示语法关键字的编辑功能。有一个基本的控件封装类,但要修改一些代码时,总是要很小心地去从头检查一遍另一模块的实现是否受影响。我的理解是,这个控件封装类的抽象不够通用,或者两个模块的相似度并不高。  基本型别偏执。这样的代码在项目中不好找,不过有类似的。项目中使用MSXML操作xml数据,在各个模块的实现中,都直接聚合了一堆MSXML的接口指针,操作xml的方法,和业务逻辑、界面响应完全混合在一起。  Switch结构。很多处又大又长的switch结构。  冗余累赘类。有两个(派生)类过于考虑以后的扩展性,而那种扩展性的需求至少在未来2、3年内是遇不上的。  夸夸其谈未来性。有一个快捷键处理模块,从项目刚开始就已经实现完成,但后来一直没被用过。项目没有开始实际编码前,超过5个人,花了2个月制订了各个模块需要暴露的COM接口,结果到现在3年了,真正实现的接口也才10个左右。  中间转手人。CMainFrame类已经成了各个模块用来转发消息的场所。一个重要的原因是界面与业务逻辑耦合,很多业务处理需要MainFrame转发到相应的界面实现类中进行处理。  狎昵关系。无论是各个Pane还是MDIClient,都与CMainFrame存在着这种双向依赖关系。  异曲同工的类。两个有交互的模块,居然各自定义了一组数据结构,用来描述现实世界中的同一种事物,中间又由CMainFrame来完成这两组数组结构之间的转换。  纯数据的类。很多时候,为了向线程函数传递一些数据(超过一个DWORD的量),就专门定义一个纯数据的类。  被拒绝的遗赠。两个平行的模块,一个类是从另一个类继承过来的,而明明有很多那被继承的类的功能,在派生类中是不需要的。呃,被继承的就是那个18000行的类。另外还有那两个需要编辑功能的模块,曾经居然也是一个类从另一个类直接继承,导致在派生类中变成不需要什么功能,就加些代码,把那部分功能屏蔽掉。  过多的注释,有一个开发人员,喜欢在自己编写的函数开头部分写上几十行注释,呃,全是算法描述和伪代码。  在公司4年,我参与过的略有规模的项目,除了这个外,另外有一个,基本是独自一人完成,代码量最高峰是7万行,后来路过不断的重构,在仍然有新特性增加的前提下,代码量缩减到4万多,现在回头看来,这个项目中代码的坏味道似乎少一些,但质量却也不行,崩溃经常发生,其他业务逻辑有问题的也不少。  所以,我就很是困惑啊,别人的项目是怎么样的情况?

Get Adobe Flash playerPlugin by wpburn.com wordpress themes