为什么在OS X上的Ruby(而不是Python)中的分叉进程中`Time.utc`会变慢?

时间:2020-12-28 16:57:30

I saw the question Why does Process.fork make stuff slower in Ruby on OS X? and was able to determine that Process.fork does not actually make tasks, in general, slower.

我看到了一个问题为什么Process.fork在OS X上的Ruby中变慢了?并且能够确定Process.fork实际上不会使任务变得更慢。

However, it does seem to make Time.utc, in particular, much slower.

但是,它似乎确实使Time.utc慢得多。

require 'benchmark'

def do_stuff
  50000.times { Time.utc(2016) }
end

puts "main: #{Benchmark.measure { do_stuff }}"

Process.fork do
  puts "fork: #{Benchmark.measure { do_stuff }}"
end

Here are some results:

以下是一些结果:

main:   0.100000   0.000000   0.100000 (  0.103762)
fork:   0.530000   3.210000   3.740000 (  3.765203)

main:   0.100000   0.000000   0.100000 (  0.104218)
fork:   0.540000   3.280000   3.820000 (  3.858817)

main:   0.100000   0.000000   0.100000 (  0.102956)
fork:   0.520000   3.280000   3.800000 (  3.831084)

One clue might be that the above takes place on OS X, whereas on Ubuntu, there doesn't seem to be a difference:

一个线索可能是上面发生在OS X上,而在Ubuntu上,似乎没有区别:

main:   0.100000   0.070000   0.170000 (  0.166505)
fork:   0.090000   0.070000   0.160000 (  0.169578)

main:   0.090000   0.080000   0.170000 (  0.167889)
fork:   0.100000   0.060000   0.160000 (  0.169160)

main:   0.100000   0.070000   0.170000 (  0.170839)
fork:   0.100000   0.070000   0.170000 (  0.176146)

Can anyone explain this oddity?

任何人都能解释这种奇怪吗?

Further investigation:

@tadman suggested that it might be a bug in the macOS / OS X time code, so I wrote a similar test in Python:

@tadman建议它可能是macOS / OS X时间码中的一个bug,所以我在Python中编写了一个类似的测试:

from timeit import timeit
from os import fork

print timeit("datetime.datetime.utcnow()", setup="import datetime")

if fork() == 0:
  print timeit("datetime.datetime.utcnow()", setup="import datetime")
else:
  pass

Again, on Ubuntu, the benchmarks are the same for the forked/main processes. On OS X, however, the forked process is now slightly faster than the main process, which is the opposite of the behavior in Ruby.

同样,在Ubuntu上,forked / main进程的基准是相同的。但是,在OS X上,分叉进程现在比主进程略快,这与Ruby中的行为相反。

This leads me to believe that the source of the "fork penalty" is in the Ruby implementation and not in the OS X time implementation.

这让我相信“fork惩罚”的来源是在Ruby实现中,而不是在OS X时间实现中。

1 个解决方案

#1


5  

As it turns out, the slowdown is due in approximately equal measure to two function calls in time.c, in the function gmtime_with_leapsecond. The two functions are tzset and localtime_r.

事实证明,在函数gmtime_with_leapsecond中,减速时间与time.c中的两个函数调用大致相同。这两个函数是tzset和localtime_r。

That discovery led me to the question Why is tzset() a lot slower after forking on Mac OS X?, of which the current question might reasonably be said to be a duplicate.

这个发现让我想到了为什么tzset()在Mac OS X上分叉后慢了很多?,其中当前的问题可能被认为是重复的。

There are two answers there, neither accepted, which point to root causes involving either

那里有两个答案,既没有被接受,也指出了涉及其中任何一个的根本原因

  • the "async-signal-safe"-ness of tzset and localtime/localtime_r, or
  • tzset和localtime / localtime_r的“异步信号安全”,或

  • Apple's use of a passive notification registry that invalidates when fork'd.
  • Apple使用被动通知注册表,在fork'd时无效。

The fact that the slowdown only occurs in years with no known leap seconds (as discovered by user that other guy) is obviously due to the fact that Ruby doesn't call gmtime_with_leapsecond when it knows that the year has no leap seconds.

事实上,减速只发生在几年没有已知的闰秒(由用户发现其他人),这显然是因为当Ruby知道年份没有闰秒时,它不会调用gmtime_with_leapsecond。

I'm not sure why there is no such slowdown in Python. One possible explanation is that my test script using fork and utcnow may not be creating a child process that calls tzset or localtime/localtime_r.

我不确定为什么Python中没有这种减速。一种可能的解释是我使用fork和utcnow的测试脚本可能没有创建调用tzset或localtime / localtime_r的子进程。

#1


5  

As it turns out, the slowdown is due in approximately equal measure to two function calls in time.c, in the function gmtime_with_leapsecond. The two functions are tzset and localtime_r.

事实证明,在函数gmtime_with_leapsecond中,减速时间与time.c中的两个函数调用大致相同。这两个函数是tzset和localtime_r。

That discovery led me to the question Why is tzset() a lot slower after forking on Mac OS X?, of which the current question might reasonably be said to be a duplicate.

这个发现让我想到了为什么tzset()在Mac OS X上分叉后慢了很多?,其中当前的问题可能被认为是重复的。

There are two answers there, neither accepted, which point to root causes involving either

那里有两个答案,既没有被接受,也指出了涉及其中任何一个的根本原因

  • the "async-signal-safe"-ness of tzset and localtime/localtime_r, or
  • tzset和localtime / localtime_r的“异步信号安全”,或

  • Apple's use of a passive notification registry that invalidates when fork'd.
  • Apple使用被动通知注册表,在fork'd时无效。

The fact that the slowdown only occurs in years with no known leap seconds (as discovered by user that other guy) is obviously due to the fact that Ruby doesn't call gmtime_with_leapsecond when it knows that the year has no leap seconds.

事实上,减速只发生在几年没有已知的闰秒(由用户发现其他人),这显然是因为当Ruby知道年份没有闰秒时,它不会调用gmtime_with_leapsecond。

I'm not sure why there is no such slowdown in Python. One possible explanation is that my test script using fork and utcnow may not be creating a child process that calls tzset or localtime/localtime_r.

我不确定为什么Python中没有这种减速。一种可能的解释是我使用fork和utcnow的测试脚本可能没有创建调用tzset或localtime / localtime_r的子进程。