一步步教你优化Delphi字串查找(转载)
一步步教你优化Delphi字串查找
2005-06-13 11:46 作者: 朱晓峰 出处: csdn开发高手 责任编辑:方舟
本人在编写离线浏览器WebSeizer的过程中,用到大量字符串处理函数,这是很耗CPU的一个处理过程,为了编写出高效率的网页解析引擎,对优化代码作了一番研究。
1 、高精度的计时函数
代码优化时需要用到精确的计时器。常用的有GetTickCount函数,可以达到毫秒级的精度。但还是很不够的,这时可以采用提高循环次数的办法。另外,还有一个精度更高的定时——“高分辨率性能计数器”(high-resolution performance counter),它提供了两个API函数,取得计数器频率的QueryPerformanceFrequency和取得计数器数值的QueryPerformanceCounter。实现原理是利用计算机中的8253,8254可编程时间间隔定时器芯片实现的。在计算机内部有三个独立的16位计数器。
计数器可以以二进制或二—十进制(BDC)计数。计数器每秒产生1193180次脉冲,每次脉冲使计数器的数字减一,产生频率是可变的,用QueryPerformanceFrequency可以得到,一般情况下都是1193180。QueryPerformance Counter可以得到当前的计数器值。所以只要你的计算机
够快,理论上精度可以达到1/1193180秒。
2 、代码优化实例
下面以一个自定义的字符串函数的为例,说明代码优化过程。
Delphi提供的字符串函数里有一个Pos函数,它的定义是:
function Pos(Substr: string; S: string): Integer;
它的作用是在字符串S中查找字符串Substr,返回值是Substr在S中第一次出现的位置,如果没有找到,返回值为0。
在本人编写WebSeizer软件(天空软件站有下载)过程中,Pos已经不能满足要求。一方面:在处理网页中的字符串时,要求对大小写不敏感,即< h t m l > 和<HTML>代表的含义完全一样。另一方面:我们还要求有一个函数,返回值是Substr在S中最后一次出现的位置,而不是第一次出现的位置。下面是这个函数的未经优化的代码。
function RightPos(const Substr,S: string): Integer;
var
iPos: Integer;
TmpStr:string;
begin
TmpStr:=s;
iPos := Pos(Substr,TmpStr); Result:=0;
//查找Substr第一次出现位置
while iPos<>0 do
begin
Delete(TmpStr,1,iPos+length(Substr)-1);
//删除已经查找过的字符
Result:=Result+iPos;
iPos := Pos(Substr,TmpStr); //查找Substr出现位置
if iPos=0 then break;
Result:=Result+length(Substr)-1;
end;
end;
这个函数里,用到了Delete函数,我们再来看一看System.pas文件里Delete函数的实现过程,请看下面代码:
procedure _LStrDelete{ var s : AnsiString; index, count : Integer };
asm
{ EAX Pointer to s }
{ EDX index }
{ ECX count }
PUSH EBX
PUSH ESI
PUSH EDI
MOV EBX,EAX
MOV ESI,EDX
MOV EDI,ECX
CALL UniqueString
MOV EDX,[EBX]
TEST EDX,EDX { source already empty:
nothing to do }
JE @@exit
MOV ECX,[EDX-skew].StrRec.length
{ make index 0-based, if not in [0 .. Length(s)-1]
do nothing }
DEC ESI
JL @@exit
CMP ESI,ECX
JGE @@exit
{ limit count to [0 .. Length(s) - index] }
TEST EDI,EDI
JLE @@exit
SUB ECX,ESI { ECX = Length(s) - index
}
CMP EDI,ECX
JLE @@1
MOV EDI,ECX
@@1:
{ move length - index - count characters from
s+index+count to s+index }
SUB ECX,EDI { ECX = Length(s) - index
- count }
ADD EDX,ESI { EDX = s+index }
LEA EAX,[EDX+EDI] { EAX = s+index+count }
CALL Move
{ set length(s) to length(s) - count }
MOV EDX,[EBX]
MOV EAX,EBX
MOV EDX,[EDX-skew].StrRec.length
SUB EDX,EDI
CALL _LStrSetLength
@@exit:
POP EDI
POP ESI
POP EBX
end;
Delete 函数中,有这两句:CALL Move和CALL_LstrSetLength。其中Move函数是将一个内存块拷贝到另一个地址,LstrSetLength函数将改变字符串的长度,其中也有对内存进行分配的代码。这些对内存进行操作的函数都是极其消耗CPU运行时间的,所以Delete函数也是一个极其消耗CPU运行时间的函数。为了尽量避免使用这些函数,我对自定义函数RightPos进行了改写。
修改后不再使用Delete及Pos函数,直接通过指针对内存操作,提高了效率。
function RightPosEx(const Substr,S: string): Integer;
var
iPos: Integer;
TmpStr: string;
i, j, lvlen: Integer;
PCharS, PCharSub: PChar;
begin
PCharS := PChar(s); //将字符串转化为PChar格式
PCharSub := PChar(Substr);
Result := 0;
lvlen := length(Substr);
for i := 0 to length(S) - 1 do
begin
for j := 0 to lvlen - 1 do
begin
if PCharS[i + j] <> PCharSub[j] then break;
end;
if j = lvlen then Result := i + 1;
end;
end;
请看第一句PCharS:=PChar(s),它的作用是将Delphi字符串强制转化为PChar 格式(PChar 是Windows中使用的标准字符串,不包含长度信息,使用0为结束标志),并得到指向PChar字符串的指针PcharS。
下面就要对自定义函数的运行时间进行测量,为了提高测量的精度,减小随机性,我们计算重复10000次所需的时间。代码如下:
var
i,len,iPos: Integer;
PerformanceCount1,PerformanceCount2,Count:int64;
begin
len:=10000; //重复次数
QueryPerformanceCounter(PerformanceCount1);//开始计数
for i:=0 to len-1 do
begin
iPos:=RightPos(’12’,Edit1.Text); //被测试的函数
end;
QueryPerformanceCounter(PerformanceCount2); //结束计数
Count:=(PerformanceCount2-PerformanceCount1);
Label1.Caption:=inttostr(iPos)+’ time=’+inttostr(Count);
End;
我的配置是Duron700,256M内存,测试中RightPos函数重复了10000遍,RightPos使用的参数为:Substr=12,S=Edit12ewew12tet。得到的测量结果是Count=217000,又对其他几个函数作了对比,结果如下:
函数名
重复次数
QueryPerformanceCounter 计数值
Pos
10000
150000
RightPos
10000
217000
RightPosEx
10000
160000
可以看出测试的结果是比较满意的,改进过的RightPosEx函数效率比RightPos高很多,考虑到测试用的字符串比较短,使用长字符串效果更明显。如果直接使用Delphi的字符串格式而不用转为PChar格式,则效率又可提高一步。但字符串格式是Delphi语言自定义的,存在兼容性问题。所以这一版本的字串搜索函数就不列出来了,读者在前面代码的基础上很容易就可写出来的。代码优化到底要执行到哪一步,是仁者见仁,智者见智的问题。有的人偏重于提高效率,有的人偏重于保证兼容性,这需要在实际使用过程中作取舍。
----
实际测试,
{*
//长字符串(大概1M左右)
1000次
BDS 2007 见Demos
(Delete) time: 323301
(CopyStr) time: 953036
(LastPosEx)Pos: 144881
time: 3665003
(Pos)Pos: 58745
time: 314023
(FastPosEx)Pos: 58745
time: 11383136
(fstAt)Pos: 58745
time: 354948
(SmartPos)Pos: 58745
time: 206409
*}
const
COUNT_NUM = 10000;
procedure TForm1.btnDeleteAndCopyStrClick(Sender: TObject);
var
i, lvlen, iPos: Integer;
PerformanceCount1, PerformanceCount2, Count: int64;
lvSubString, lvSourceString: string;
begin
lvlen := COUNT_NUM; //重复次数
lvSourceString := edtSourceString.Text;
if chkUseMemoSource.Checked then
begin
lvSourceString := mmoSourceString.Text;
lvlen := lvlen div 10;
end;
QueryPerformanceCounter(PerformanceCount1); //开始计数
lvSubString := lvSourceString;
for i := 0 to lvlen - 1 do
begin
Delete(lvSubString, 1, 1);
end;
QueryPerformanceCounter(PerformanceCount2); //结束计数
Count := (PerformanceCount2 - PerformanceCount1);
mmoMessage.Lines.Add('(Delete) time: ' + inttostr(Count));
QueryPerformanceCounter(PerformanceCount1); //开始计数
lvSubString := lvSourceString;
for i := 0 to lvlen - 1 do
begin
lvSubString := CopyStr(lvSubString, 2, Length(lvSubString));
end;
QueryPerformanceCounter(PerformanceCount2); //结束计数
Count := (PerformanceCount2 - PerformanceCount1);
mmoMessage.Lines.Add('(CopyStr) time: ' + inttostr(Count));
end;
procedure TForm1.btnPosTestClick(Sender: TObject);
var
i, lvlen, iPos: Integer;
PerformanceCount1, PerformanceCount2, Count: int64;
lvSubString, lvSourceString: string;
begin
lvlen := COUNT_NUM; //重复次数
lvSourceString := edtSourceString.Text;
lvSubString := edtPosTest.Text;
if chkUseMemoSource.Checked then
begin
lvSourceString := mmoSourceString.Text;
lvlen := lvlen div 10;
end;
QueryPerformanceCounter(PerformanceCount1); //开始计数
for i := 0 to lvlen - 1 do
begin
iPos := LastPosEx(lvSubString, lvSourceString); //被测试的函数
end;
QueryPerformanceCounter(PerformanceCount2); //结束计数
Count := (PerformanceCount2 - PerformanceCount1);
mmoMessage.Lines.Add('(LastPosEx)Pos: ' + inttostr(iPos) + sLineBreak + ' time: ' + inttostr(Count));
QueryPerformanceCounter(PerformanceCount1); //开始计数
for i := 0 to lvlen - 1 do
begin
iPos := Pos(lvSubString, lvSourceString); //被测试的函数
end;
QueryPerformanceCounter(PerformanceCount2); //结束计数
Count := (PerformanceCount2 - PerformanceCount1);
mmoMessage.Lines.Add('(Pos)Pos: ' + inttostr(iPos) + sLineBreak + ' time: ' + inttostr(Count));
QueryPerformanceCounter(PerformanceCount1); //开始计数
for i := 0 to lvlen - 1 do
begin
iPos := FastPosEx(PChar(lvSourceString), Length(lvSourceString), PChar(lvSubString), Length(lvSubString)); //被测试的函数
end;
QueryPerformanceCounter(PerformanceCount2); //结束计数
Count := (PerformanceCount2 - PerformanceCount1);
mmoMessage.Lines.Add('(FastPosEx)Pos: ' + inttostr(iPos) + sLineBreak + ' time: ' + inttostr(Count));
QueryPerformanceCounter(PerformanceCount1); //开始计数
for i := 0 to lvlen - 1 do
begin
iPos := fstAt(lvSubString, lvSourceString); //被测试的函数
end;
QueryPerformanceCounter(PerformanceCount2); //结束计数
Count := (PerformanceCount2 - PerformanceCount1);
mmoMessage.Lines.Add('(fstAt)Pos: ' + inttostr(iPos) + sLineBreak + ' time: ' + inttostr(Count));
QueryPerformanceCounter(PerformanceCount1); //开始计数
for i := 0 to lvlen - 1 do
begin
iPos := SmartPos(lvSubString, lvSourceString); //被测试的函数
end;
QueryPerformanceCounter(PerformanceCount2); //结束计数
Count := (PerformanceCount2 - PerformanceCount1);
mmoMessage.Lines.Add('(SmartPos)Pos: ' + inttostr(iPos) + sLineBreak + ' time: ' + inttostr(Count));
end;
转载于:https://www.cnblogs.com/DKSoft/archive/2008/05/25/1207093.html
一步步教你优化Delphi字串查找(转载)相关推荐
- c字串和String字串的区别
今天总结一下c/c++字串的类别. c + + 中,有两种字串 { 从 c 沿袭过来的 c 字串 c + + 的 s t l 资源提供的 s t r i n g 类型的字串 c++中,有两种字串\le ...
- DELPHI加密字串(异或运算加密)
首先有两个自定的转换函数: function myStrToHex(s:string):string; //字串转16进制 var TmpStr:string; i:integer; begin Tm ...
- [转]OllyDBG 入门系列(二)-字串参考
标 题: [原创]OllyDBG 入门系列(二)-字串参考 作 者: CCDebuger 时 间: 2006-02-14,13:34 链 接: http://bbs.pediy.com/showthr ...
- OllyDBG 入门系列(二)-字串参考
标 题: [原创]OllyDBG 入门系列(二)-字串参考 作 者: CCDebuger 时 间: 2006-02-14,13:34:43 链 接: http://bbs.pediy.com/show ...
- 第六周作业(等值字串,KMP匹配,大整数相乘,最长公共子串,判断两个字符串是否匹配,最长回文子串,年号字串)
目录 1.等值字串 2.KMP匹配 3.大整数相乘 4.最长公共子串 5.判断两个字符串是否匹配 6.最长回文字串 7.年号字串 补发一下,原来忘记发了. 1.等值字串 [问题描述]如果字符串的一个子 ...
- 很不错的教程一步步教你如何写Makefile
[转载:原文地址:http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=408225] 陈皓 概述 -- 什么是makefile?或许很多 ...
- LeetCode中等题之无重复字符的最长字串
题目 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1: 输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 &q ...
- 一步步教你轻松学朴素贝叶斯模型算法理论篇1
一步步教你轻松学朴素贝叶斯模型理论篇1 (白宁超2018年9月3日17:51:32) 导读:朴素贝叶斯模型是机器学习常用的模型算法之一,其在文本分类方面简单易行,且取得不错的分类效果.所以很受欢迎,对 ...
- 机器学习:一步步教你理解反向传播方法
机器学习:一步步教你理解反向传播方法 时间 2016-09-13 00:35:59 Yong Yuan's blog 原文 http://yongyuan.name/blog/back-propa ...
最新文章
- 第十一周学习总结--助教
- 简易promise的实现(二)
- 用固定收敛标准特征迭代次数法实现分类是不是一个巧合?
- python与其他编程语言对比优点_Python编程不同于其他编程语言的优点
- php 系统平均负载,Linux_解析Linux系统的平均负载概念,一、什么是系统平均负载(Load a - phpStudy...
- java string类型详解_Java字符串类型详解
- 【python】Macbook的Anaconda查看、创建和管理python环境
- 如何在Web浏览器中查看XML文件
- 第二代计算机网络主要贡献,2010级计算机网络试题
- 南方周末:阿里巴巴的大数据梦
- bootstrap今天的学习心得
- 阿甘博客文章写法与教学方法
- python 去掉字符串第一个字符_10 个 Python 字符串处理技巧
- 通过SecureCRT工具从远程Linux服务器下载文件到本地Windows
- 三种方法实现多级撤消/重做
- php中联合运算符,PHP-串联运算符
- 什么U盘启动盘制作工具是纯净版的,没有捆绑软件和广告的?
- 二阶系统阶跃响应实验_二阶系统的阶跃响应实验报告
- 手机控制电脑 linux,手机控制电脑 Splashtop远程管理体验
- stack的常见用法