怨minor GC的中断时间。增量垃圾收集器的最大中断时间要小于minor GC的中断时间。如果你在minior GC不存在中断时间问题,就不用担心major GC的中断时间。
我还介绍过为Ruby实现增量垃圾收集器的一人小技巧。我们得到一系列“黑色非保护”类。为了快速执行垃圾清理,我们准备了一个非保护的bitmap代表非保护类,一个独立的已标记bitmap代表已标记类。使用两个bitmaps的逻辑可以获取“黑色非保护”类。
增量垃圾收集器中断时间评测
为了计量垃圾收集器产生在的中断时间,让我们使用?gc_tracergem. gc_tracer gem引用了 GC::Tracer模块在垃圾收集事件中获取相关参数。gc_tracergem将每个参数都保存在文件中。
垃圾收集事件包括以下事件:
如我前面所解释,Ruby的垃圾收集包含两个阶段:“标记附件”和“清理”阶段。“开始“是”开始标记附件“事件,”标记结束“是”标记附件结束“事件,”标记结束“也现时意味着”开始清理阶段",当然“清理结束”代表”清理阶段“结束,也意味着垃圾清理过程的结束。
”创建对象“和”释放对象”很容易理解:对象分配和对象释放事件。
我们通过“进入”和“离开”事件计量中断时间。增量垃圾收集(增量标记和延迟清理)引入中断标记和清理阶段。“进入“是”进入垃圾清理“???件,”离开“是”离开垃圾清理“事件。?
下图显示了当前增量垃圾收集器的事件时间。

垃圾收集器事件
我们可以计量每一个事件的当前时间(在 Linux 上当前时间是通过 gettimeofday() 获得)。所以我们可以通过“进入”和“离开”事件计量垃圾收集器的中断时间。
我使用?ko1-test-app?做为我们中断时间的基准。ko1-test-app 是我们的一个英雄“Aaron Patterson"为我写的一个小的 rails app
为了使用 gc_tracer,我添加了一条命名为“test_gc_tracer”的规则,如下:
diff --git a/perf.rake b/perf.rake
index f336e33..7f4f1bd 100644
--- a/perf.rake
+++ b/perf.rake
@@ -54,7 +54,7 @@ def do_test_task app
? body.close
?end
?
-task :test do
+def test_run
? app = Ko1TestApp::Application.instance
? app.app
?
@@ -67,6 +67,22 @@ task :test do
? }
?end
?
+task :test do
+? test_run
+end
+
+task :test_gc_tracer do
+? require 'gc_tracer'
+? require 'pp'
+? pp GC.stat
+? file = "log.#{Process.pid}"
+? GC::Tracer.start_logging(file, events: %i(enter exit), gc_stat: false) do
+ test_run
+? end
+? pp GC.stat
+? puts "GC tracer log: #{file}"
+end
+
?task :once do
? app = Ko1TestApp::Application.instance
? app.app
运行 bundle exec rake test_gc_tracer KO1TEST_CNT=30000。我们通过数值“30000”指定模拟30,000个请求。我们可以在文件“log.xxxx”里得到结果(xxxx是程序的进程ID)。文件如下
type? tick? major_by? ? ? gc_by? have_finalizer? immediate_sweep state
enter? 1419489706840147? ? ? 0? ? newobj? 0? ? 0? ? sweeping
exit? 1419489706840157? ? ? 0? ? newobj? 0? ? 0? ? sweeping
enter? 1419489706840184? ? ? 0? ? newobj? 0? ? 0? ? sweeping
exit? 1419489706840195? ? ? 0? ? newobj? 0? ? 0? ? sweeping
enter? 1419489706840306? ? ? 0? ? newobj? 0? ? 0? ? sweeping
exit? 1419489706840313? ? ? 0? ? newobj? 0? ? 0? ? sweeping
enter? 1419489706840612? ? ? 0? ? newobj? 0? ? 0? ? sweeping
...
在我的环境下,这里有 1,142,907 行。
“type”列是事件类型,tick 是当前时间(gettimeofday()的输出结果,epoc总时间,单位是毫秒)。通过这些信息我们可以获取垃圾收集器的中断时间。通过前两行我们可以计算出中断时间为10 微秒 (1419489706840157 - 1419489706840147)。
以下小脚本显示每次中断时间。
enter_tick = 0
open(ARGV.shift){|f|
? f.each_line{|line|
? e, tick, * = line.split(/\s/)
? case e
? when 'enter'
? ? enter_tick = tick.to_i
? when 'exit'
? ? st = tick.to_i - enter_tick
? ? puts st if st > 100 # over 100 us
? else
? ? # puts line
? end
? }
}
这里有很多行,这个脚本打印了 100 微秒内的中断次数。
下图显示了统计结果

在分代垃圾收集器中产生了7个较大的中断时间,他们应该是由“major GC”产生的。最大中断时间大概15毫秒(15Kus),不管怎样,增量垃圾收集器减少了最大中断时间(差不多2ms(2Kus)),太好了。
Ruby 2.2 通过引入增量垃圾收集算法缩短了垃圾收集的中断时间。
请注意,增量垃圾收集器不是一个银弹。我曾解释过增量垃圾收集并不改善“吞吐量”,这意味着如果请求很长并产生多个 major GC,反应时间并不会得到有效改善。使用增量垃圾收集器并不会缩短总的中断时间。
请在 Heroku 尝试你的应用,期待你的挑战!
英文原文:Incremental Garbage Collection in Ruby 2.2