最近在改写一个加密算法,算法不可避免用到了随机数,但是Lazarus默认的随机种子初始化函数Randomize是依赖于GetTickCount的,而GetTickCount的时间精度是毫秒级的(windows平台实际大约是16ms级),日常是够用的,但在加密算法中就显得太过粗糙了。
关于GetTickCount的时间精度,详见Delphi中的延时和在Lazarus中分析Windows和Linux的延时。
在以上文章中有提到,windows平台下可以使用QueryPerformanceFrequency和QueryPerformanceCounter获取高精度的时间,事实上是微秒级的时间(本机实测是0.1微秒级,即百纳秒)。
考虑跨平台的话,如何获取非windows平台的高精度时间就变得很有必要了。
解决方案#
搜索资料及查看Lazarus源码,找到了fpgettimeofday和clock_gettime两个函数。
fpgettimeofday#
fpgettimeofday实际上就是UNIX平台的gettimeofday,只引用unix单元即可。其定义如下:
1
|
function fpgettimeofday(tp: ptimeval;tzp:ptimezone):cint; external name 'FPC_SYSC_GETTIMEOFDAY';
|
其参数ptimeval的定义如下:
1
2
3
4
5
6
7
8
9
10
|
timeval = record
tv_sec:time_t;
{$ifdef CPUSPARC64}
tv_usec:cint;
{$else CPUSPARC64}
tv_usec:clong;
{$endif CPUSPARC64}
end;
ptimeval = ^timeval;
TTimeVal = timeval;
|
另外,unix平台的GetTickCount64用到了fpgettimeofday:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
function GetTickCount64: QWord;
var
tp: TTimeVal;
{$IFDEF HAVECLOCKGETTIME}
ts: TTimeSpec;
{$ENDIF}
begin
{$IFDEF HAVECLOCKGETTIME}
if clock_gettime(CLOCK_MONOTONIC, @ts)=0 then
begin
Result := (Int64(ts.tv_sec) * 1000) + (ts.tv_nsec div 1000000);
exit;
end;
{$ENDIF}
fpgettimeofday(@tp, nil);
Result := (Int64(tp.tv_sec) * 1000) + (tp.tv_usec div 1000);
end;
|
从其命名及使用中可确定,使用fpgettimeofday获取到的时间精度为微秒级。
clock_gettime#
GetTickCount64也用到了clock_gettime,其参数ptimespec的定义如下:
1
2
3
4
5
6
|
timespec = record
tv_sec : time_t;
tv_nsec : clong;
end;
ptimespec = ^timespec;
TTimeSpec = timespec;
|
从其命名及使用从其命名及使用中可确定,使用clock_gettime获取到的时间精度为纳秒级。
注意:clock_gettime并非是在unix单元中定义的,反而是在linux和freebsd中分别定义的,因此,使用入要配合合适的编译指令共同使用。
关于Randomize#
在查看源码过程中发现,linux平台的Randomize并非是依赖于GetTickCount,实现如下:
1
2
3
4
|
Procedure Randomize;
Begin
randseed:=longint(Fptime(nil));
End;
|
Fptime的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
function Fptime(tloc:pTime_t): Time_t; [public, alias : 'FPC_SYSC_TIME'];
{$if defined(generic_linux_syscalls) or defined(FPC_USEGETTIMEOFDAY)}
VAR tv : timeval;
tz : timezone;
retval : longint;
begin
Retval:=do_syscall(syscall_nr_gettimeofday,TSysParam(@tv),TSysParam(@tz));
If retval=-1 then
Fptime:=-1
else
Begin
If Assigned(tloc) Then
TLoc^:=tv.tv_sec;
Fptime:=tv.tv_sec;
End;
End;
{$else FPC_USEGETTIMEOFDAY}
begin
Fptime:=do_syscall(syscall_nr_time,TSysParam(tloc));
end;
{$endif FPC_USEGETTIMEOFDAY}
|
从中可以看出,其时间精度也是微秒级的。
疑问:Fptime虽然是System单元通过多层{$I}指令引入的,但却无法直接调用,具体原因尚未进行深入探究。