新物网

当前位置:首页 > 百科

百科

delphi中的代码优化和delphi7Fastmm配备

时间:2023-10-20 21:35:33 雅雅
去Delphi专题讲座FastMM 第三方内存管理器适用于Delphi,它在海外已经很有名了,在中国也有很多人使用或希望应用,即使是 Borland Delphi2007也放弃了原来备受指责的内存管理器,改为FastMMM.然而,代码优化的复杂性和缺乏
去Delphi专题讲座

FastMM 第三方内存管理器适用于Delphi,它在海外已经很有名了,在中国也有很多人使用或希望应用,即使是 Borland Delphi2007也放弃了原来备受指责的内存管理器,改为FastMMM.
然而,代码优化的复杂性和缺乏 FastMM 中文文档导致国内很多人在使用过程中遇到了很多问题,有些人因此放弃了应用,我还在最近的一个新项目中使用Fastmm,通过探索和研究,我遇到了很多问题。

为什么使用FastMMM?
第一个主要原因是FastMM的性能接近delphi默认内存管理器的两倍,可以进行简单的测试,操作下面的代码:
var
I: Integer;
Tic: Cardinal;
S: string;
begin
tic := GetTickCount;
try
for I := 0 to 100000 do
begin
SetLength(S, I 100);
edt1.Text := S;
end;
finally
SetLength(S, 0);
tic := GetTickCount - Tic;
MessageDlg('Tic = ' IntToStr(Tic), mtInformation, [mbOK], 0);
end;
end;
在我IBM 在T23书中,使用FastMM4(FastMM的最新版本)大约需要3300ms,而使用默认内存管理器大约需要6200ms,FastMM4的性能提高了88%.
Fastmm共享内存管理器使用简单可靠的第二个原因。当一个应用软件有几个控制模块(exe和dll)构成时,控制模块之间的calloc自变量,如string传输,是一个很大的情况。默认情况下,每个模块都由自己的内存管理器分配,内存管理器分配的内存必须在此内存管理器中安全释放,否则会出现内存错误。因此,如果在一个控制模块分配的内存中释放了另一个控制模块,则很容易出现内存错误。要解决这些问题,需要使用共享内存管理器,使每个模块使用相同的内存管理器。为了解决这些问题,我们需要使用共享内存管理器,这样每个模块都可以使用相同的内存管理器。Delphi的默认共享内存管理器是BORLNDMM.DLL,这种内存管理器不可靠,而且经常发生,程序发布时必须与DLL一起发布。Fastmm共享内存管理器的功能不适用于DLL,而且更可靠。
第三个主要原因是Fastmm还具有一些功能来帮助软件开发,如内存泄漏检测功能,可以识别程序过程中是否存在未正确释放的内存。

三、有什么问题?
如果我们开发和设计应用程序,只有一个exe控制模块,那么应用fastmm是一件非常简单的事情,只需要使用fastmm.pas(最新版本是FastMM4.pas)作为工程文件的第一个uses模块,如:

program Test;
uses
FastMM4,

然而,一般来说,每个人的应用程序都是由一个exe控制模块和几个dll组成的。这样,当我们传输calloc自变量,如string变量时,就会出现问题,例如,下面的测试代码由exe和dll组成:

library test; // test.dll
uses
FastMM4, …;
procedure GetStr(var S: string; const Len: Integer); stdcall;
begin
SetLength(S, Len); // 释放内存
FillChar(S[1], Len, ‘A’);
end;
exports
GetStr;
-------------------------------------
program TestPrj;
uses
FastMM4, …;
//------------------
unit mMain; // 检测页面

procedure TForm1.btnDoClick(Sender: TObject);
var
I: Integer;
S: string;
Begin
try
for I := 1 to 10000 do
begin
GetStr(S, I 1);
edt1.Text := S;
Application.ProcessMessages;
end;
finally
SetLength(S, 0);
end;
end;

当btndoclick的整个过程第二次实施时,很容易出现内存错误。为什么?带引用计数的delphi字符串,就像插口自变量一样,一旦引用计数为0,内存往往会自动增加。在btndoclick链接中,启用Getstr的整个过程,用Setlength为S分配一段运行内存。此时,字符串数组的引用计数为1,然后实施edt1.Text := S句,字符串数组的引用计数为2,循环系统再次启用GetStr向S释放内存,使原字符串的引用计数减少1,然后执行edt1.Text := S,原始字符串引用计数为0,此时将被释放(请注意,TestPrj).exe释放,而不是去testt,.dll发布),但此时没有出错。循环系统实施后,字符串数组的引用计数为2,但实施setlength(S, 0)以后,该字符串数组被edt1所使用.Text引入,引用计数为1,第二次实施btnDoClick时,实施edt1,.Text := S时,将之前引用计数为1的字符串引用计数减少到0,就会释放出来,此时,就会出现内存错误。
因此,我们可以看到,在另一个控制模块中释放其他控制模块分配的内存并不一定会立即出现内存错误。然而,如果经常实施,就会出现内存错误。这种不确定性的不正确性含有很大的秘密性,在调试过程中往往不会出现,但在实际应用过程中,没有具体的分析就找不到原因。
要解决这个问题,我们应该从根本原因开始。这些根本原因是代码优化。
一、Delphi代码优化
Delphi应用软件可以使用三个运行内存区:全球运行内存区、堆栈、堆栈、全球运行内存区存储局部变量、堆栈传输参数及其参数及其函数公式中的临时变量,由编译程序自动管理,如字符串数组、目标、动态数组也从堆中分配,代码优化是指堆内存管理方法,也就是说,从堆中释放内存和从堆中释放分布的内存(以下简称内存的分布和释放)。
众所周知,一个过程只有一堆栈,所以很容易误以为一个过程只有一堆。但事实上,一个过程不仅有一个默认设置堆(默认设置尺寸1MB)的系统分配,还有几个客户堆,每个堆都有自己的返回值。delphi代码优化管理的恰好是独立设置的堆。delphi以单链表的形式将另一堆堆分成几个大小不一的块,具体的内存实际操作都在各种块上。
delphi将代码优化定义为内存分配(Get)、释放出来(Free)和分配(Realloc)。内存管理器实际上是这三种实现的组成部分,delphi在system单元中重新定义了这个内存管理器TMemoryManger:

PMemoryManager = ^TMemoryManager;
TMemoryManager = record
GetMem: function (Size: Integer): Pointer;
FreeMem: function (P: Pointer): Integer;
ReallocMem: function (P: Pointer; Size: Integer): Pointer;
end;
从而了解,delphi的内存管理器是一个 TMemoryManager 该记录有三个域,分别偏向于内存的分配、释放和分配方法。
System模块还重新定义了一个自变量 MemoryManager :
MemoryManager: TMemoryManager = (
GetMem: SysGetMem;
FreeMem: SysFreeMem;
ReallocMem: SysReallocMem);
该变量是delphi流程的内存管理器,在默认情况下,这个内存管理器的三个域分别偏向于GETMEM.SysGetMememe是在INC中实现的。、SysFreeMem、SysReallocMem。这个内存管理器的自变量只能在system中使用.从pas中可以看出,system模块带来了三种访问变量的方法:

// 载入内存管理器,也就是说,输入MemoryManger
procedure GetMemoryManager (var MemMgr: TMemoryManager);
// 组装内存管理器(即用新的内存管理器更换默认内存管理器)
procedure SetMemoryManager (const MemMgr: TMemoryManager);
// 内存管理器是否已安装(即默认内存管理器是否已更换)
function IsMemoryManagerSet: Boolean;

共享内存管理器
共享内存管理器是什么?
共享内存管理器是应用程序的所有模块。exe和dll都使用相同的内存管理器来处理操作内存。这样,内存的分配和释放就完成了相同的内存管理器,不会出现内存错误问题。
共享内存管理器是应用程序的所有模块。exe和dll都使用相同的内存管理器来处理操作内存。这样,内存的分配和释放就完成了相同的内存管理器,不会出现内存错误问题。
如何共享内存管理器?
从以上分析可以看出,既然是应用同一个内存管理器,只需要建立一个独立的内存管理器控制模块(dll),所有其他控制模块都使用该模块的内存管理器来分配和增加内存。Delphi7默认设置就是这样,在我们的应用指导下创建dll工程时,这样一段话会出现在工程文件中:
{Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
这句话提醒我们,ShareMem 是 BORLNDMM.DLL 共享内存管理器的接口模块,让我们来看看这个Sharemem,这个模块文档非常简洁,会有这样的声明:

const
DelphiMM = 'borlndmm.dll';
function SysGetMem (Size: Integer): Pointer;
external DelphiMM name '@Borlndmm@SysGetMem$qqri';
function SysFreeMem(P: Pointer): Integer;
external DelphiMM name '@Borlndmm@SysFreeMem$qqrpv';
function SysReallocMem(P: Pointer; Size: Integer): Pointer;
external DelphiMM name '@Borlndmm@SysReallocMem$qqrpvi';
这一声明确保了BORLNDMM.静态数据载入DLL。
Initialization在Sharemem中就是这样的编码:
if not IsMemoryManagerSet then
InitMemoryManager;
首先区分运行内存管理工具是否已组装(即默认运行内存管理工具是否已更换),如果没有,然后重置运行内存管理工具,InitMemoryManager也很简单(删除了无意义的代码):

procedure InitMemoryManager;
var
SharedMemoryManager: TMemoryManager;
MM: Integer;
begin
// force a static reference to borlndmm.dll, so we don't have to LoadLibrary
SharedMemoryManager.GetMem:= SysGetMem;
MM: = GetModuleHandle (DelphiMM);
SharedMemoryManager.GetMem:= GetProcAddress (MM,'@Borlndmm@SysGetMem$qqri');
SharedMemoryManager.FreeMem:= GetProcAddress (MM,'@Borlndmm@SysFreeMem$qqrpv');
SharedMemoryManager.ReallocMem:= GetProcAddress (MM, '@Borlndmm@SysReallocMem$qqrpvi');
SetMemoryManager (SharedMemoryManager);
end;
该函数定义了运行内存管理工具的目标,并配备了一个域偏向Borlndmm.完成dll的三个函数公式,然后用SetmemoryManager代替默认操作内存管理工具。
这样,无论哪个控制模块,Sharemem都需要作为工程项目的第一个uses模块,所以每个模块的Sharemeninitialization都是最初强制执行的。换句话说,虽然每个模块的内存管理工具目标不一样,但是,运行内存管理工具的三个函数指针都偏向于borlndmm.因此,所有模块的内存分配和释放都是在Borlndmm中完成的.dll内部结构完成后,不会出现跨控制模块增加内存造成的问题。
Fastmm是如何实现共享内存管理工具的?
Fastmm原则上实施了一种非常简单的方法,即创建运行内存管理工具,然后将运行内存管理工具的地址放置在所有控制模块可以在一个过程中包含的部分。这样,在建立运行内存管理工具之前,其他控制模块首先检查是否有其他控制模块将运行内存管理工具放在这里,如果使用此运行内存管理工具,否则,创建一个新的运行内存管理工具,并在这里放置详细的地址。这样,该进度的所有控制模块都采用了运行内存管理工具,完成了运行内存管理工具的共享。
此外,操作内存管理工具不确定哪个控制模块建立,所有模块,只要Fastmm作为工程文件的第一个uses模块,可能是操作内存管理工具的创始人,关键是看到应用程序的载入顺序,第一个载入模块将成为操作内存管理工具的创始人。
此外,操作内存管理工具不确定哪个控制模块建立,所有模块,只要Fastmm作为工程文件的第一个uses模块,可能是操作内存管理工具的创始人,关键是看到应用程序的载入顺序,第一个载入模块将成为操作内存管理工具的创始人。
那么,FastMM是如何完成的呢?
开启 FastMM4.pas(Fastmm的最新版本),或者看看它的Initialization部分:

{Initialize all the lookup tables, etc. for the memory manager}
InitializeMemoryManager;
{Has another MM been set, or has the Borland MM been used? If so, this file
is not the first unit in the uses clause of the project's .dpr file.}
if CheckCanInstallMemoryManager then
begin
InstallMemoryManager;
end;
InitializeMemoryManager 一些自变量是复位的,完成后,启用checkcaninstalmemorymanager检查fastmm是否是工程项目的第一个uses模块,如果回到true,则启用instalmemorymanager组装fastmm自己的内存管理工具,每个人都按顺序选择这个函数公式的关键编码进行分析:
{Build a string identifying the current process}
LCurrentProcessID: = GetCurrentProcessId;
for i := 0 to 7 do
UniqueProcessIDString [8 - i]:= HexTable [((LCurrentProcessID shr (i * 4)) and $F)];
MMWindow: = FindWindow ('STATIC', PChar (@UniqueProcessIDString [1]));
首先,获取进度ID,转换为16进制字符串数组,然后搜索以字符串数组为对话框名称窗口的字符串数组。
如果在这个过程中没有对话框,那么MMWindow 将回到0,然后建立对话框:
MMWindow: = CreateWindow ('STATIC', PChar (@UniqueProcessIDString [1]),
WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
建立这个对话框有什么用?再看下面的代码:<>
if MMWindow
0 then
SetWindowLong (MMWindow, GWL_USERDATA, Integer (@NewMemoryManager));
NewMemoryManager.Getmem: = FastGetMem;
NewMemoryManager.FreeMem: = FastFreeMem;
NewMemoryManager.ReallocMem: = FastReallocMem;
查看MSDN就可以知道了,每个对话框都有一个32位值,可以建立其应用程序中使用的值,可以通过使用以GWL_USERDATA为主要参数的SetWindowLong进行调整,也可以通过使用GWL_USERDATA为主要参数的GetWindowLong进行调整。因此,我们清楚地知道,Fastmm将交换运行内存管理工具的地址存储在此值上,以便其他控制模块可以通过Getwindowlong输入此值,从而获得交换操作内存管理工具:

NewMemoryManager: = PMemoryManager (GetWindowLong (MMWindow, GWL_USERDATA)) ^;
根据以上分析,我们可以知道,FastMM 比borland更适合推广共享内存管理工具,borland的完成方法促使应用软件必须使用BORLNDMM.DLL一起公布,FastMM的完成方式不需要任何DLL的大力支持。
然而,上述摘录代码忽略了一些编译选项。事实上,Fastmm的共享内存管理工具需要使用,在每个模块编译程序时必须使用Fastmm4.在pas模块上打开一些编译选项:
{$define ShareMM} //打开共享内存管理工具,其他两个编译选项生效的前提
{$define ShareMMIfLibrary} ///允许dll共享其运行内存管理工具。如果此功能没有定义,只有exe控制模块才能在应用程序中建立和数据共享其运行内存管理工具,因为静态数据载入的dll总是比exe早,因此,如果一个dll可以被静态数据载入,则需要打开选项,否则可能会出错
{$define AttemptToUseSharedMM} 操作内存管理工具允许一个模块应用于其他控制模块的交换
FastMM4中的这个编译选项.FastMM4Optionsspas目录.在inc文档中有一个概念定义表明,只是这个定义已经被注释掉了,所以,可以撤销注释打开这个编译选项,也可以在你的项目目录下创建一个.inc文档(如FastShareMMM.inc),在此文件中加载这些编译选项,然后,FastMM4.pas开头“”{$Include FastMM4Options.inc}以前加“”之前加“”{$include FastShareMM.inc}“是的,这样,不同类型的工程项目可以使用不同的FastShareM.inc文档。

五、代码优化在线程同步中
代码优化在线程同步条件下非常安全吗?显然,如果不采取一定的对策,肯定是不安全的。borland已经充分考虑了这样的事情,所以在delphi的system中.环境变量Ismultithread在pas中被重新定义。这个环境变量标志着现阶段是否属于线程同步自然环境。那么,它是如何工作的呢?打开TThread.可以看到,Create函数公式代码调用Beginthread创建线程,而Beginthread将Ismultithread设置为Truead.
我们再来谈谈GETMEMEM。.INC的SysGetMem、SysFreeMem、可以看到Sysreallocmem的完成,所有这些句子都在进行中:
if IsMultiThread then EnterCriticalSection(heapLock);
换言之,在线程同步条件下,内存的分配和释放将在临界区域同步,以确保安全。
Fastmm采用CUP命令lock实现同步,作为其他指令的前缀,可以在指令实施过程中锁定系统总线。当然,只有Ismultithread是True时,它才会同步。
Ismultithread的概念是system.pas环境变量,每个模块(exe或dll)都有自己的Ismultithread自变量,而且,默认Fasle,只有在这个模块中创建用户线程,才能将这个自变量设置为true,因此,每个人都在exe中创建线程,只会将exe中的Ismultithread设置为true,而不是将其他dll板块中的ismultithread设置为true。但是前面说过,如果我们使用静态数据载入的dll,这个dll将比exe更早被系统载入。此时,第一个被载入的dll将创建一个操作内存管理工具并共享。此操作内存管理工具将用于其他控制模块。换句话说,exe的Ismultithread自变量并不影响应用程序的操作内存管理工具。操作内存管理工具仍然认为现阶段不是线程同步的自然环境,因此不同步,容易出现内存错误。
解决这些问题是尽一切办法在线程同步环境中设置每个模块ISMultithread作为True,以确保无论哪个控制模块具体创建了运行内存管理工具,运行内存管理工具都知道现阶段是线程同步自然环境,必须同步解决。
解决这些问题是尽一切办法在线程同步环境中设置每个模块ISMultithread作为True,以确保无论哪个控制模块具体创建了运行内存管理工具,运行内存管理工具都知道现阶段是线程同步自然环境,必须同步解决。
还行,windows为我们的dll理解应用软件创建过程提供了一个系统。dllmain函数是dll链接库的入口函数,delphi包装此入口函数,提供TDLProc的函数类型和自变量DLProc类型:

TDLLProc = procedure (Reason: Integer); // System定义.pas
// Sysinitt定义.pas:
var
DllProc: TDllProc;

当系统进程Dlldllmain时,delphi最终将使用dllproc进行处理,dllproc可以由我们自己的tdllproc完成。但是当过程创建了一个新的过程时,计算机操作系统使用Reason=DLL_THREAD_ATTACH为主要参数使用Dllmain,使Dllphi最终使用Dllproc作为主要参数使用,因此我们只需要完成一个新的TDlproc来完成ThisDlproc,并将Dlproc偏向ThisDlproc,但在ThisDllProc中,当收到DLL_THREAD_ATTACH时,将Ismultithread设置为True。完成代码如下:

library xxx;
var
OldDllProc: TDLLProc;
procedure ThisDllProc(Reason: Integer);
begin
if Reason = DLL_THREAD_ATTACH then
IsMultiThread := True;
if Assigned(OldDllProc) then
OldDllProc(Reason);
end;
begin
OldDllProc := DllProc;
DllProc := ThisDllProc;
ThisDllProc(DLL_PROCESS_ATTACH);


六、汇总
本文将讨论以下问题:
1、为什么要使用Fastmm?
2、跨控制模块传输calloc自变量会出现什么问题?原因有哪些
3、delphi代码优化和内存管理器的原因是什么?
4、为什么要分享内存管理器,delphi和Fastmm是如何实现内存管理器交换的?
5、如何在线程同步条件下实现内存管理器的同步?
6、在线程同步条件下,如何跨控制模块设置ISMultithread自变量,以确保内存管理器同步

FastMM的使用要规范,在控制模块开发设计时,应进行以下工作:
1、打开编译选项{$define ShareMM}、{$define ShareMMIfLibrary}、{$define AttemptToUseSharedMM}
2、FastMM(4).作为每个工程文件的第一个uses模块,pas

3、假如是dll,需要解决DLL_THREAD_ATTACH用于参数Dllmain,Ismultithread用于True

 1>  Fastmm是开源软件, 从 http://sourceforge.net/projects/fastmm
 
 下载安卓
2>  Replacementent文件夹的名称 BorlndMM DLL/Precompiled/for Delphi IDE/Performance/BorlndMM.dll,将Delphi/Bin中的相应文档替换为加速IDE3> Enviroment->Library->Directories-> Library Path

 
加上Fassmm路径,我把它放在Delphi安装文件下,立即设为$(DELPHI)/FastMM
4> 在您的项目文档中,Project->View Source打开,uses 第一个添加FastMM4模块的
5> 如果有Memory,编译程序来操作你的程序 leak,过程结束时会有一个提示框。




可以取消提示框
开启FastMM4Options.inc文档。在文件的最后添加如下所示的代码:
{$define Release}
{$ifdef Release}
  {Specify the options you use for release versions below}
  {$undef FullDebugMode}
  {$undef CheckHeapForCorruptio
  {$define ASMVersio
  {$undef EnableMemoryLeakReporting}
  {$undef UseOutputDebugString}
  {$undef LogErrorsToFile}
  {$undef LogMemoryLeakDetailToFile}
{$else}
  {Specify the options you use for debugging below}
  {$define FullDebugMode}
  {$define EnableMemoryLeakReporting}

{$define UseOutputDebugString}{$endif}