最近在改写一个加密算法,算法不可避免用到了随机数,但是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}
指令引入的,但却无法直接调用,具体原因尚未进行深入探究。