如何在 Ruby 中以毫秒为单位计时操作?

85
我想找出一个特定函数使用了多少毫秒,但我搜遍了各种地方,没有找到在 Ruby 中获取毫秒精度时间的方法。您如何做到这一点?在大多数编程语言中,这只是像以下代码一样简单的事情:
start = now.milliseconds
myfunction()
end = now.milliseconds
time = end - start
10个回答

94
你可以使用 Ruby 的 Time 类。例如:
t1 = Time.now
# processing...
t2 = Time.now
delta = t2 - t1 # in seconds
现在,delta是一个float对象,您可以获取类提供的最细致的结果。

2
这是一种简单的解决方案,但更好的答案是使用Process.clock_gettime。请参阅https://blog.dnsimple.com/2018/03/elapsed-time-with-ruby-the-right-way/。 - Guy C
而更好的答案是使用Benchmark.measure。它的实现使用单调时钟。 - Jonatan Lindén

64

你还可以使用内置的Benchmark.measure函数:

require "benchmark"
puts(Benchmark.measure { sleep 0.5 })

输出:

0.000000   0.000000   0.000000 (  0.501134)

1
我喜欢使用内置库的想法,但前三个数字是什么(0.000000)?_更新:_在文档中查找到它是用户 CPU 时间,系统 CPU 时间以及两者之和。 - lyonsinbeta
1
是的。如果您使用 Benchmark.bm,它们会被标记出来。有时候查看时间花费在哪里很有用。在这里,您可以看到该块只是空闲状态(总共0秒,实际0.5秒)。 - Simon Perepelitsa
17
还有一个 Benchmark.realtime 方法,它会返回一个易于理解的单精度浮点数 :) - Sergio Tulentsev

39

使用Time.now(返回挂钟时间)作为基准线存在一些问题,可能导致意外的行为。这是由于挂钟时间受到诸如插入闰秒或时间调整等更改的影响。

如果在测量过程中插入了闰秒,则会误差1秒。同样地,根据本地系统条件,您可能需要处理夏令时、运行速度较快或较慢的时钟,甚至时钟倒回时间,导致负持续时间和许多其他问题。

解决此问题的方法是使用不同类型的时钟:单调时钟。这种类型的时钟具有与挂钟不同的属性。它单调递增,即永远不会后退并以恒定速率增加。因此,它不代表挂钟(即从墙上的时钟读取的时间),而是可以与稍后的时间戳进行比较以获取差异的时间戳。

在Ruby中,您可以使用Process.clock_gettime(Process::CLOCK_MONOTONIC)来使用此类时间戳,如下所示:

t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# => 63988.576809828

sleep 1.5 # do some work

t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# => 63990.08359163

delta = t2 - t1
# => 1.5067818019961123
delta_in_milliseconds = delta * 1000
# => 1506.7818019961123

Process.clock_gettime 方法返回一个带有小数秒的时间戳。实际返回的数字没有定义的意义(您不能依赖它)。但是,您可以确保下一次调用将返回一个更大的数字,并通过比较这些值来获取实际时间差异。

这些特性使该方法成为测量时间差异的最佳选择,而无需在最不适当的时间(例如,在新年除夕时分开启闰秒)看到程序失败。

此处使用的 Process::CLOCK_MONOTONIC 常量在所有现代 Linux、BSD 和 macOS 系统以及 Windows 的 Linux 子系统中都可用。但它尚未在“原始” Windows 系统上提供。在那里,您可以使用 GetTickCount64 系统调用代替 Process.clock_gettime,它还返回窗口系统(≥ Windows Vista,Windows Server 2008)中毫秒为粒度的计时器值。

在 Ruby 中,您可以像这样调用此函数:

require 'fiddle'

# Get a reference to the function once
GetTickCount64 = Fiddle::Function.new(
  Fiddle.dlopen('kernel32.dll')['GetTickCount64'],
  [],
  -Fiddle::TYPE_LONG_LONG # unsigned long long
)

timestamp = GetTickCount64.call / 1000.0
# => 63988.576809828

3
这是唯一应该被接受的答案,更别说被采纳了。 - Rob Kinyon
也许值得添加一些关于操作系统支持的细节:SUSv3到4,Linux 2.5.63,FreeBSD 3.0,NetBSD 2.0,OpenBSD 3.4,macOS 10.12。 - Guy C
在这个解决方案出现之前,原始问题和答案都已经提供了。为什么这应该是唯一“可接受”的答案? - CitizenX

14

您应该查看基准测试模块以进行性能测试。然而,作为一个快速而简单的计时方法,您可以使用以下代码:

def time
  now = Time.now.to_f
  yield
  endd = Time.now.to_f
  endd - now
end

注意使用 Time.now.to_f,与 to_i 不同的是,它不会截取到秒。


6
这里将施法时间转换为浮点数是不必要的。 - Sergio Tulentsev
另外,在“endd”中有一个拼写错误。 - nightpool
@nightpool 这样拼写是因为 end 是一个关键字。 - sepp2k
1
然后像“end_time”这样做,不要拼错单词,那样很草率。 - nightpool

2
此外,我们可以创建一个简单的函数来记录任何代码块:
def log_time
  start_at = Time.now

  yield if block_given?

  execution_time = (Time.now - start_at).round(2)
  puts "Execution time: #{execution_time}s"
end

log_time { sleep(2.545) } # Execution time: 2.55s

1

使用 Time.now.to_f


1
"absolute_time gem是Benchmark的即插即用替代品,但使用本地指令使其更加准确。"

1
或者使用 hitimes gem,在 Windows 上可能比 absolute_time 有更高的粒度... - rogerdpack

0

我有一个宝石可以对你的 Ruby 方法(实例或类)进行分析 - https://github.com/igorkasyanchuk/benchmark_methods

不再需要像这样的代码:

t = Time.now
user.calculate_report
puts Time.now - t

现在你可以做:

benchmark :calculate_report # in class

然后只需调用您的方法

user.calculate_report

0

使用Time.now.to_i可以返回从1970年01月01日开始经过的秒数。有了这个信息,你就可以做...

date1 = Time.now.to_f
date2 = Time.now.to_f
diff = date2 - date1

使用这个方法,你将会得到秒级别的差异。如果你想要毫秒级别的结果,只需要在代码中添加即可。

diff = diff * 1000

2
.to_i gives an integer and would truncate the milliseconds. This would work if you just changed it to .to_f - Ryan Taylor
1
是的,正如Ryan所说; 对于我的投票抱歉,但这实际上有一个赞成票,我感到有必要进行反击。将以秒为单位的“整数”时间差乘以1000以获取毫秒?来吧,MARC.RS,你肯定只是在逗我们玩! :-) - Andrew Hodgkinson
不,我只是尽力而为。 - MARC.RS

0

如果你使用

date = Time.now.to_i

你正在获取以秒为单位的时间,这远非准确,特别是当你计时小代码块时。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接