天马行空(笨笨)
希望下辈子不要那么笨了
Review: Delphi 2007 for Win32 (Beta) - part two 
      编译器的新变化

From Hallvard's Blog

http://hallvards.blogspot.com/2007/03/review-delphi-2007-for-win32-beta-part_06.html
翻译:monkeyking(www.cnblogs.com/monkeyking
转载请注明出处。翻译匆忙,如有不妥之处请留言共同探讨。

新的published和$M+对比

新的编译器警告“W1055 PUBLISHED caused RTTI ($M+) to be added to type '%s'”非常有趣。它解决了我们之前讨论过的一个问题。在以前的编译器版本中,如果你编写这样的代码:

type
 TMyClass = class
 private
    FName: string;
 published
    property Name: string read FName write FName;
 end;

 

published属性不会产生RTTI ,它在后台被默认当作public属性来对待。这主要是因为TmyClass不是从一个使用了$M+编译指示字的类继承下来(比如TPersistent),它自己内部也没有使用$M+。现在,当你在Delphi2007里编译相同的代码时你会得到这样的警告:

[DCC Warning] ThSort.pas(13): W1055 PUBLISHED caused RTTI ($M+) to be added to type 'TMyClass'

 

这说明,尽管$M+被漏掉了,但是编译器仍然会将它作为标准published属性来处理,并为它产生RTTI。在类声明前面添加一个$M+指示字,就不会再有这个警告了。如果你只是想使用一个Public属性,那么就把published改成public好了。

$DYNAMICBASE ASLR 

编译器现在接受一个新的编译指示字——$DYNAMICBASE ON/OFF,它对应的命令行参数为“-dynamicbase”。它相当于已经存在的编译指示字$SetPEOptFlags的一个快捷方式。$DYNAMICBASE ON等同于$SetPEOptFlags $40$40Windows中的定义为:

const IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = $0040;
 // The DLL can be relocated at load time.

 

SetPEOptFlags指示字将在PE头中重开辟一个DllCharacteristics字段。它被用于Windows的装载程序确定某个被装载模块(比如DLL或者BPL)的大小。$40Windows Vista中有特殊的意义,它将使用一个名为“随机地址空间分布(Address Space Layout RandomizationASLR)”的特性。使用该特性后,OS会在装载模块时适当的随机偏移一定的地址(实际上偏移地址大致在0-255之间)以取代现在的这种精确装载于某个镜像基地址上的做法。这样做的主要目的是为了安全和抵御黑客针对DLL代码进行缓冲区溢出攻击。下面的链接讲解了更多关于这方面的知识http://blogs.msdn.com/michael_howard/archive/2006/05/26/address-space-layout-randomization-in-windows-vista.aspx

 

-description的命令行选项

 

编译器同样有一个新的命令行选项“-description:<string>”,它对应与$DESCRIPTION编译指示字。用于在PE头中设定模块描述入口。

 

RTL中有什么新内容?

 

运行时库(RTL)包含了Delphi系统中所有编译器层面之上的最底层的构建模块。SysInitSystem单元与编译器和链接器的内部紧紧捆绑着。当包(package)被使用时,SysInit被编译到每一个模块中(Exe和包文件BPL),System单元仅仅被编译到rtl100.bpl中。这就是为什么它们会被分离为两个单元文件。

 

这是个基本的背景知识。那么现在,到底有什么新变化呢?事实上,SysInit并没有变化。我猜测这说明SysInit单元很稳定,不需要任何修改。这可是个好消息。接下来我们深入分析一下System单元所作的改变。

 

System单元的变化

 

有一个名字非常好记的全局布尔变量出现——NeverSleepOnMMThreadContentionSystem.pas单元的实现部分引用了一个getmem.inc文件,该文件使用了新的内存管理器FastMM进行内存管理,而这个变量正式作用于它的。下面这段注释与变量一同出现:

 

{Set this variable to true to employ a "busy waiting" loop instead of putting the thread to sleep if a thread contention occurs inside the memory manager. This may improve performance on multi-CPU systems with a relatively low thread count, but will hurt performance otherwise.}
NeverSleepOnMMThreadContention: Boolean;

 

FastMM需要保护共享资源时,它会使用轻量级的原语操作(例如汇编指令lock cmpxchg)替代重型的OS级别的临界对象API。如果遇到冲突(某个资源已经被其他线程锁定),它通常会进入休眠状态,释放CPU控制权,并允许其他线程完成自己的工作和释放该资源锁。

 

如同上面的注释中所提到的,在一些多CPU的机器上(这正在变为主流),采用在循环中不停的检查某个“锁”是否可用(也可称作spin-lock)的方法,等待另外一个CPU执行完线程并释放“锁”时,会更加有效率。我们一起来看看这个逻辑的Pascal代码:

 

    while LockCmpxchg(0, 1, @MediumBlocksLocked) <> 0 do
    begin
      if not NeverSleepOnMMThreadContention then
      begin
        Sleep(InitialSleepTime);
        if LockCmpxchg(0, 1, @MediumBlocksLocked) = 0 then
          break;
        Sleep(AdditionalSleepTime);
      end;
    end;

 

另外一个改进是关于spin-lock的汇编版本(默认情况下使用该版本)。它使用了pause x86指令,以便更好的兼容处理器和操作系统。

 asm
 //...
 {The pause instruction improves spinlock performance}
 pause
 //...
end;

 

此外,通过对一些汇编代码采用类似method-extract重构方法(重新分配大内存块的逻辑已经被重构到ReallocateLargeBlock例程的内部了),FastMM的代码体积好像略有减少(我没有仔细计算过)。

这时,看起来它对大片段内存块的支持逻辑是有所改进的。Pierre在代码里做了以下注释:

{Is this large block segmented? I.e. is it actually built up from more than one chunk allocated through VirtualAlloc? (Only used by large blocks.)}

 

已优化过的RTL代码

正如Steve Threfethen在博客中写的那样,RTL已经包含了一些新的更新并用一些FastCode中代码替换原有的功能。当你比较字符串时,编译器魔法中System单元的例程 _LStrCmp 将被使用,这会成为代码性能至关重要的一环。Pierre le Richie为这个例程编写了一个新的更快的版本(依据FastCode大赛中CompareStr挑战赛的获胜代码编写),并提交到Quality Central中,编号为 #31328。该代码已经集合到RTL中。

 

_LStrCmp是进行字符串比较时都要用到的例程,包括字符串操作符>>==<><<=。在PierreQC报告里,他建议修改编译器以便于调用不同的具有针对性的函数(_LStrEqual)。多数情况下,它只需要对比字符串的长度即可得知结果。但是这样的修改会影响二进制文件.dcu的兼容性,所以这项建议没有包含在Spacely中,但是很有可能应用于HighlanderHighlander或许会成为BDS2007)。

其他来源于FastCode的代码应用于SysUtils单元。BDS2006中,UpperCaseLowerCase都已经使用来源于FastCode的代码了(作者:Aleksandr Sharahov)。而在Delphi2007里,John O'Harrow又为它们开发了更快的版本。此外CompareStrStrLen也使用了由Pierre le Riche编写的高效代码。

另外,FileAgeFileSearch函数也被优化。FileAge函数现在使用更加有效率的API GetFileAttributesExA (如果该API能够使用)替代原有的FindFirst/FindClose。如果FileSearchName参数为空值,它也会直接返回。这两个变化有助于改进编译器和链接器的速度。

 

最后,CheckForDuplicateUnits中使用了哈希表的逻辑思想,在装载包(package)时有更多的优化效果。这段代码在BDS2006中有很大的改进,但是现在,它会更快。当应用程序在启动时需要动态装载许多包时,它有助于缩短启动时间(比如Delphi IDE)。

桌面窗口管理器API

DwmAPI单元是一个新出现的单元,它由VistaDWMAPI.DLL导出。在其他平台上,这些代码会返回错误代码E_NOTIMPL。本单元中的所有常量和代码都是Delphi IDEVCL中新添加进来的有关Vista新功能和Glass特殊效果的基础。比如里面就有这些例程DwmExtendFrameIntoClientArea DwmUpdateThumbnailProperties,大多数情况下VCL已经屏蔽了我们直接使用这些基本例程的需求,但我们还是应该知道在哪找到它们。

Math单元和完整的布尔值计算(complete boolean evaluation

Math单元里的三个InRange函数(重载的IntegerInt64Double版本)有两个很有趣的变化。主要是代码的布置从(BDS2006版本):

function InRange(const AValue, AMin, AMax: Integer): Boolean;
begin
 Result := (AValue >= AMin) and (AValue <= AMax);
end;

 

变成了(Delphi2007版本):

function InRange(const AValue, AMin, AMax: Integer): Boolean;
var
 A,B: Boolean;
begin
 A := (AValue >= AMin);
 B := (AValue <= AMax);
 Result := B and A;
end;

 

代码的变化,迫使编译器在设置Result变量前,计算出所有布尔表达式的值。这可以减少指令的条件判断后跳转(conditional jump)次数,以优化执行性能(一个明显的例子,如果Inrange返回True,它都需要计算出所有表达式)。编译出来的代码的不同之处可以参照下面(这是由BDS2006产生的汇编代码):

Result := (AValue >= AMin) and (AValue <= AMax);
//0040841C 3BD0             cmp edx,eax
//0040841E 7F04             jnle $00408424
//00408420 3BC8             cmp ecx,eax
//00408422 7D03             jnl $00408426
//00408424 33C0             xor eax,eax
//00408426 C3               ret

 

新版本函数的汇编代码如下:

 A := (AValue >= AMin);
//0040842C 3BD0             cmp edx,eax
//0040842E 0F9EC2           setle dl
 B := (AValue <= AMax);
//00408431 3BC8             cmp ecx,eax
//00408433 0F9DC0           setnl al
 Result := B and A;
//00408436 22C2             and al,dl
end;
//00408438 C3               ret

 

如你所见,第二段代码没有任何分支,而第一段代码由两个条件分支指令。现在处理器在无分支代码上能工作的更有效率,所以第二段代码的实际效果更好。也许某天我们会再遇到这个问题,此时还有另外一种方法来解决它——使用 $B+ 编译指示字。它会命令编译器完整的计算布尔值。

 

评论的第二部分到此结束,第三部分即将发布。

posted on 2007-06-28 11:21  天马行空(笨笨)  阅读(519)  评论(0编辑  收藏  举报