中标准计时的一点猎取,一步步教您什么样优化

2019-08-24 14:45 来源:未知

自个儿在编排离线浏览器WebSeizer的历程中,用到大方字符串管理函数,那是很耗CPU的三个管理进程,为了编写出高功效的网页解析引擎,对优化代码作了一番商讨。

自己在编排离线浏览器WebSeizer的进度中,用到大方字符串管理函数,那是很耗CPU的一个管理进度,为了编写出高功效的网页分析引擎,对优化代码作了一番研讨。

以下有所代码运转情状:Windows 二〇〇四, AMD(Escort) Core(TM) 2 Duo CPU E8400 @ 3.00GHz 2.99GHz,2.96GB内存

  1 、高精度的计时函数

1 、高精度的计时函数

听大人说汇总互连网的局地作品,准确计时首要有以下三种方法

  代码优化时索要用到标准的电火花计时器。常用的有GetTickCount函数,能够达到纳秒级的精度。但还是很非常不够的,这时可以行使升高循环次数的不二等秘书技。别的,还会有一个精度更高的定期——“高分辨率质量计数器”(high-resolution performance counter),它提供了多少个API函数,获得计数器频率的QueryPerformanceFrequency和收获计数器数值的QueryPerformanceCounter。达成原理是利用Computer中的8253,8254可编制程序时间间隔放大计时器微芯片实现的。在微机内部有多少个单身的13个人计数器。

代码优化时索要用到标准的计时器。常用的有GetTickCount函数,能够完毕皮秒级的精度。但依然很相当不足的,那时能够动用提升循环次数的主意。其他,还大概有一个精度越来越高的按期——“高分辨率品质计数器”(high-resolution performance counter),它提供了七个API函数,获得计数器频率的QueryPerformanceFrequency和获得计数器数值的QueryPerformanceCounter。达成原理是利用Computer中的8253,8254可编制程序时间距离电火花计时器微电路达成的。在处理器内部有八个单身的16个人计数器。

1 调用WIN API中的GetTickCount

[DllImport("kernel32")]
static extern uint GetTickCount();

从操作系统运营到现行反革命所经过的阿秒数,精度为1皮秒,经简单测验开采实际截断误差在概略在15ms左右

缺欠:再次回到值是uint,最大值是2的35回方,由此只要服务器延续开机差非常的少49天未来,该办法得到的回来值会归零

用法:

?

1
2
3
uint s1 = GetTickCount();
Thread.Sleep(2719);
Console.WriteLine(GetTickCount() - s1); //单位毫秒

  计数器能够以二进制或二—十进制(BDC)计数。计数器每秒爆发11934柒拾柒回脉冲,每一趟脉冲使计数器的数字减一,发生频率是可变的,用QueryPerformanceFrequency能够得到,一般情形下都以1193480。QueryPerformance Counter能够获得当前的计数器值。所以一旦您的微型电脑
够快,理论上精度能够完毕1/1一九三二80秒。

计数器能够以二进制或二—十进制(BDC)计数。计数器每秒爆发1一九三二八十次脉冲,每回脉冲使计数器的数字减一,发生频率是可变的,用QueryPerformanceFrequency可以收获,一般景况下都以1193580。QueryPerformance Counter能够拿走当前的计数器值。所以只要你的Computer

2 调用WIN API中的timeGetTime 推荐

[DllImport("winmm")]
static extern uint timeGetTime();

常用于多媒体停车计时器中,与GetTickCount类似,也是回来操作系统运营到以往所经过的飞秒数,精度为1皮秒。

貌似暗中同意的精度不唯有1皮秒(不一致操作系统有所差异),供给调用timeBeginPeriod与timeEndPeriod来安装精度

[DllImport("winmm")]
static extern void timeBeginPeriod(int t);
[DllImport("winmm")]
static extern void timeEndPeriod(int t);

破绽:与GetTickCount同样,受再次回到值的最大位数限制。

用法:

?

1
2
3
4
5
timeBeginPeriod(1);
uint start = timeGetTime();
Thread.Sleep(2719);
Console.WriteLine(timeGetTime() - start); //单位毫秒
timeEndPeriod(1);

  2 、代码优化实例

够快,理论上精度能够高达1/1壹玖叁肆80秒。

3 调用.net自带的法子System.Environment.TickCount

猎取系统运维后通过的阿秒数。经反编写翻译估计它大概也是调用的GetTickCount,可是它的再次回到值是int,而GetTickCount与timeGetTime方法的原型中重返值是DWOOdysseyD,对应C#中的uint,难道.NET对System.Environment.TickCount其余还做了如何管理么?
www.weide1946.com,劣势:与GetTickCount同样,受重临值的最大位数限制。

用法:

?

1
2
3
int aa = System.Environment.TickCount;
Thread.Sleep(2719);
Console.WriteLine(System.Environment.TickCount - aa); //单位毫秒

 

:经过测验,发掘GetTickCount、System.Environment.TickCount也足以用timeBeginPeriod与timeEndPeriod来安装精度,最高可将精度进步到1皮秒。不知是何等来头?

  下边以三个自定义的字符串函数的为例,说北周码优化进程。

2 、代码优化实例

4 调用WIN API中的QueryPerformanceCounter

[DllImport("kernel32.dll ")]
static extern bool QueryPerformanceCounter(ref   long lpPerformanceCount);

用于获取高精度电磁照拂计时器(如若存在这么的沙漏)的值。微软对那几个API解释正是每分钟有些计数器增进的数值。
借使设置的硬件不辅助高精度电火花计时器,函数将赶回false要求同盟另三个API函数QueryPerformanceFrequency。

[DllImport("kernel32")]
static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency);

QueryPerformanceFrequency重回硬件支持的高精度计数器的成效,如若设置的硬件不援助高精度放大计时器,函数将再次回到false。

用法:

?

1
2
3
4
5
6
7
long a = 0;
QueryPerformanceFrequency(ref a);
long b = 0, c = 0;
QueryPerformanceCounter(ref b);
Thread.Sleep(2719);
QueryPerformanceCounter(ref c);
Console.WriteLine((c - b) / (decimal)a); //单位秒

精度为百10%秒。何况由于是long型,所以不真实上面多少个API位数远远不够的难题。

症结:在一篇小说看到,该API在严格地进行节约方式的时候结果偏慢,超频形式的时候又偏快,何况用电瓶和接电源的时候效果还不均等(台式机)
原稿地址:
未通过超频等测量检验,若是是真正,这该API出来的结果就也许禁止。

  Delphi提供的字符串函数里有二个Pos函数,它的概念是:

上边以贰个自定义的字符串函数的为例,表明清码优化进度。

5 使用.net的System.Diagnostics.Stopwatch类 推荐

Stopwatch 在基础放大计时器机制中对沙漏的刻度实行计数,从而度量运维时刻。借使设置的硬件和操作系统扶助高分辨率质量的计数器,则 Stopwatch 类将动用该计数器来衡量运营时刻;不然,Stopwatch 类将采取系总括数器来衡量运维时刻。使用 Frequency 和 IsHighResolution 多少个静态字段能够规定达成 Stopwatch 计时的精度和分辨率。

实际它里面纵使将QueryPerformanceCounter、QueryPerformanceFrequency八个WIN API封装了一晃,假如硬件扶助高精度,就调用QueryPerformanceCounter,假诺不协理就用Date提姆e.Ticks来总结。

用法:

?

1
2
3
4
5
Stopwatch sw = new Stopwatch();
sw.Start();
Thread.Sleep(2719);
sw.Stop();
Console.WriteLine(sw.ElapsedTicks / (decimal)Stopwatch.Frequency);
function Pos(Substr: string; S: string): Integer;

Delphi提供的字符串函数里有贰个Pos函数,它的定义是:

6 使用CPU时间戳进行越来越高精度计时

原著地址:

该方法的原理笔者不是很明亮,硬件知识太贫乏了。精度是ns

在C#中要用该办法必得先创造三个托管C 项目(因为要内嵌汇编),编写翻译成DLL供c#调用,有一些麻烦。

C 代码:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// MLTimerDot.h
 
#pragma once
 
using namespace System;
 
namespace MLTimerDot {
 
//得到计算机启动到现在的时钟周期
unsigned __int64 GetCycleCount(void)
{
_asm _emit 0x0F
_asm _emit 0x31
}
 
 
//声明 .NET 类
public __gc class MLTimer
{
public:
MLTimer(void)
{
 
}
 
//计算时钟周期
UInt64 GetCount(void)
{
return GetCycleCount();
}
 
};
}

C#调用:

?

1
2
3
4
5
6
7
long a = 0;
QueryPerformanceFrequency(ref a);
 
MLTimerDot.MLTimer timer = new MLTimerDot.MLTimer();
ulong ss= timer.GetCount();
Thread.Sleep(2719);
Console.WriteLine((timer.GetCount() - ss) / (decimal)a);

症结:和QueryPerformanceCounter同样,结果不太平静。

 

自己的下结论:常规应用下timeGetTime完全够用了,将精度调到1阿秒,大部分情况都够用。System.Diagnostics.Stopwatch由于调用方便,也援用使用。

  它的功能是在字符串S中寻找字符串Substr,重临值是Substr在S中第三遍面世的职务,若无找到,再次来到值为0。

function Pos(Substr: string; S: string): Integer;

  在自个儿编写WebSeizer软件(天空软件站有下载)进度中,Pos已经不能满意供给。一方面:在拍卖网页中的字符串时,要求对大小写不灵活,即< h t m l > 和<HTML>代表的含义完全一致。另一方面:我们还须求有二个函数,再次来到值是Substr在S中最终一遍出现的地点,实际不是第贰次面世的岗位。上边是那么些函数的未经优化的代码。

它的效应是在字符串S中追寻字符串Substr,重回值是Substr在S中首先次出现的地点,若无找到,再次来到值为0。

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;

在自家编写WebSeizer软件(天空软件站有下载)进程中,Pos已经不可能满足须求。一方面:在管理网页中的字符串时,要求对大小写不敏感,即< h t m l > 和代表的含义完全等同。另一方面:大家还要求有一个函数,重返值是Substr在S中最后二遍面世的地点,并不是第三次出现的岗位。下边是这些函数的未经优化的代码。

  这几个函数里,用到了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;
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 函数中,有这两句:CALL Move和CALL_LstrSetLength。其中Move函数是将二个内部存款和储蓄器块拷贝到另叁个地点,LstrSetLength函数将改变字符串的长度,在那之中也许有对内部存款和储蓄器举办分配的代码。那个对内部存款和储蓄器实行操作的函数都以但是消耗CPU运维时刻的,所以Delete函数也是四个特别消耗CPU运维时刻的函数。为了尽量防止使用那些函数,小编对自定义函数RightPos实行了改写。

本条函数里,用到了Delete函数,大家再来看一看System.pas文件里Delete函数的兑现进度,请看下边代码:

  修改后不再行使Delete及Pos函数,直接通过指针对内部存款和储蓄器操作,进步了频率。

 

function RightPosEx(const Substr,S: string): Integer;
var
 iPos: Integer;
 TmpStr:string;
 i,j,len: Integer;
 PCharS,PCharSub:PChar;
begin
 PCharS:=PChar(s); //将字符串转化为PChar格式
 PCharSub:=PChar(Substr);
 Result:=0;
 len:=length(Substr);
 for i:=0 to length(S)-1 do
 begin
  for j:=0 to len-1 do
  begin
   if PCharS[i j]<>PCharSub[j] then break;
  end;
  if j=len then Result:=i 1;
end;
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;

  请看率先句PCharS:=PChar(s),它的机能是将Delphi字符串强制转化为PChar 格式(PChar 是Windows中使用的正式字符串,不带有长度音讯,使用0为了却标识),并收获针对性PChar字符串的指针PcharS。

Delete 函数中,有这两句:CALL Move和CALL_LstrSetLength。当中Move函数是将贰个内部存储器块拷贝到另一个地点,LstrSetLength函数将退换字符串的尺寸,个中也会有对内部存款和储蓄器实行分红的代码。这一个对内部存款和储蓄器进行操作的函数都以无比消耗CPU运转时刻的,所以Delete函数也是三个最为消耗CPU运营时刻的函数。为了尽量防止使用那一个函数,笔者对自定义函数RightPos进行了改写。

  上边将要对自定义函数的运作时刻张开衡量,为了升高度量的精度,减小随机性,我们总括重复一千0次所需的日子。代码如下:

修改后不复选取Delete及Pos函数,直接通过指针对内存操作,升高了效用。

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,又对任何多少个函数作了对待,结果如下:

function RightPosEx(const Substr,S: string): Integer;
            var
             iPos: Integer;
             TmpStr:string;
             i,j,len: Integer;
             PCharS,PCharSub:PChar;
            begin
             PCharS:=PChar(s); //将字符串转化为PChar格式
             PCharSub:=PChar(Substr);
             Result:=0;
             len:=length(Substr);
             for i:=0 to length(S)-1 do
             begin
              for j:=0 to len-1 do
              begin
               if PCharS[i j]<>PCharSub[j] then break;
              end;
              if j=len then Result:=i 1;
            end;
函数名 重复次数 QueryPerformanceCounter 计数值
Pos 10000 150000
RightPos 10000 217000
RightPosEx 10000 160000

 

  能够见见测验的结果是相比满意的,革新过的RightPosEx函数作用比RightPos高相当多,考虑到测量试验用的字符串非常的短,使用长字符串效果更简明。假若向来行使Delphi的字符串格式而不用转为PChar格式,则效能又可进步一步。但字符串格式是Delphi语言自定义的,存在包容性难点。所以这一本子的字串找出函数就不列出来了,读者在前面代码的底蕴上很轻便就可写出来的。代码优化到底要施行到哪一步,是智者见智,仁者见仁的标题。有的人偏重于升高效用,有的人偏重于保障包容性,那亟需在事实上使用进程中作取舍。

请看率先句PCharS:=PChar(s),它的机能是将Delphi字符串强制转化为PChar 格式(PChar 是Windows中使用的业内字符串,不带有长度新闻,使用0为了却标识),并拿走针对性PChar字符串的指针PcharS。

上面就要对自定义函数的周转时刻进行度量,为了增长衡量的精度,减小随机性,我们计算重复一千0次所需的年月。代码如下:

 

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;

...

版权声明:本文由韦德娱乐1946_韦德娱乐1946网页版|韦德国际1946官网发布于网络编程,转载请注明出处:中标准计时的一点猎取,一步步教您什么样优化