我在 Rubymine 8.0.3 中运行 Ruby 2.2
我的机器运行的是配备英特尔酷睿 i7-4710MQ 的 Windows 7 专业版
我已经能够在这台机器上使用 C++、Java、Python 和 JS 实现 ~411 ns 的精度,但似乎找不到在 Ruby 中实现这种性能的方法,因为内置的时间库仅适用于毫秒。
我可以对测试进行编程以容忍这种降低的精度,但是是否可以合并 Windows QPC API 以改进执行时间的评估?
我用于确定时钟滴答精度的测试代码如下:
numTimes = 10000
times = Array.new(numTimes)
(0...(numTimes)).each do |i|
times[i] = Time.new
end
durations = []
(0...(numTimes - 1)).each do |i|
durations[i] = times[i+1] - times[i]
end
# Output duration only if the clock ticked over
durations.each do |duration|
if duration != 0
p duration.to_s + ','
end
end
下面的代码包含此处找到的 QPC
require "Win32API"
QueryPerformanceCounter = Win32API.new("kernel32",
"QueryPerformanceCounter", 'P', 'I')
QueryPerformanceFrequency = Win32API.new("kernel32",
"QueryPerformanceFrequency", 'P', 'I')
def get_ticks
tick = ' ' * 8
get_ticks = QueryPerformanceCounter.call(tick)
tick.unpack('q')[0]
end
def get_freq
freq = ' ' * 8
get_freq = QueryPerformanceFrequency.call(freq)
freq.unpack('q')[0]
end
def get_time_diff(a, b)
# This function takes two QPC ticks
(b - a).abs.to_f / (get_freq)
end
numTimes = 10000
times = Array.new(numTimes)
(0...(numTimes)).each do |i|
times[i] = get_ticks
end
durations = []
(0...(numTimes - 1)).each do |i|
durations[i] = get_time_diff(times[i+1], times[i])
end
durations.each do |duration|
p (duration * 1000000000).to_s + ','
end
此代码返回我的机器上 ~22-75 微秒的时钟周期之间的持续时间
您可以使用Process::clock_gettime
获得更高的精度:
返回 POSIX
clock_gettime()
函数返回的时间。
下面是一个包含Time.now
的示例
times = Array.new(1000) { Time.now }
durations = times.each_cons(2).map { |a, b| b - a }
durations.sort.group_by(&:itself).each do |time, elements|
printf("%5d ns x %dn", time * 1_000_000_000, elements.count)
end
输出:
0 ns x 686
1000 ns x 296
2000 ns x 12
3000 ns x 2
12000 ns x 2
18000 ns x 1
这是与Process.clock_gettime
相同的示例:
times = Array.new(1000) { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
输出:
163 ns x 1
164 ns x 1
164 ns x 9
165 ns x 6
165 ns x 22
166 ns x 39
166 ns x 174
167 ns x 13
167 ns x 129
168 ns x 95
168 ns x 32
169 ns x 203
169 ns x 141
170 ns x 23
170 ns x 37
171 ns x 30
171 ns x 3
172 ns x 24
172 ns x 10
174 ns x 1
175 ns x 2
180 ns x 1
194 ns x 1
273 ns x 1
2565 ns x 1
这是一个快速的并排比较:
array = Array.new(12) { [Time.now, Process.clock_gettime(Process::CLOCK_MONOTONIC)] }
array.shift(2) # first elements are always inaccuate
base_t, base_p = array.first # baseline
printf("%-11.11s %-11.11sn", 'Time.now', 'Process.clock_gettime')
array.each do |t, p|
printf("%.9f %.9fn", t - base_t, p - base_p)
end
输出:
Time.now Process.clo
0.000000000 0.000000000
0.000000000 0.000000495
0.000001000 0.000000985
0.000001000 0.000001472
0.000002000 0.000001960
0.000002000 0.000002448
0.000003000 0.000002937
0.000003000 0.000003425
0.000004000 0.000003914
0.000004000 0.000004403
这是运行在Intel Core i7上的OS X上的Ruby 2.3,不确定Windows。
为避免浮点转换导致的精度损失,您可以指定另一个单位,例如:
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
#=> 191519383463873
Time#nsec
:
numTimes = 10000
times = Array.new(numTimes)
(0...(numTimes)).each do |i|
# nsec ⇓⇓⇓⇓
times[i] = Time.new.nsec
end
durations = (0...(numTimes - 1)).inject([]) do |memo, i|
memo << times[i+1] - times[i]
end
puts durations.reject(&:zero?).join $/
Ruby Time 对象存储自纪元以来的纳秒数。
从 Ruby 1.9.2 开始,Time 实现使用有符号的 63 位整数、Bignum 或 Rational。整数是自纪元以来的纳秒数,可以表示 1823-11-12 到 2116-02-20。
您可以使用Time#nsec
最准确地访问纳秒部分。
$ ruby -e 't1 = Time.now; puts t1.to_f; puts t1.nsec'
1457079791.351686
351686000
如您所见,在我的OS X机器上,它只能精确到微秒。 这可能是因为OS X缺乏clock_gettime()
。