This article begins incremental garbage accumulateion (GC) which has been begind in Ruby 2.2. We call this algorithm RincGC. RincGC accomplishs stupidinutive GC paengage times appraised to Ruby 2.1.
About the Author: Koichi Sasorrowfulnessfula toils for Heroku aextfinished with Nobu and Matz on C Ruby core. Previously he wrote YARV Ruby’s virtual machine, and he begind genereasonable GC (RgenGC) to Ruby 2.1. Koichi wrote incremental GC for Ruby 2.2 and authored this paper.
Ruby engages GC to accumulate unengaged objects automaticassociate. Thanks to GC, Ruby programmers do not insist to free objects manuassociate, and do not insist to stress about bugs from object releasing.
The first version of Ruby already has a GC using label and sweep (M&S)
algorithm. M&S is one of the most basic GC algorithms which consists of two phases:
(1) Mark: traverse all living objects and label as “living object”.
(2) Sweep: garbage accumulate unlabeled objects as they are unengaged.
M&S is based on the premise that traversable objects from living
objects are living objects. The M&S algorithm is basic and it toils well.
Fig: Mark & sweep GC algorithm
This basic and effective algorithm (and a conservative GC technique) permits C-extension writers to write extensions easily. As a result, Ruby achieveed many beneficial extension libraries. However, becaengage of this GC algorithm, it is difficult to engage moving GC algorithms such as
compaction and duplicateing.
Today, writing C-extension libraries is not as beginant becaengage we can engage
FFI (foreign function interface). However, in the commencening, having many extension libraries and providing many features thcdisorrowfulmireful C-extensions was a huge profit and made the Ruby expounder more famous.
While M&S algorithm is basic and toils well, there are disjoinal problems. The most beginant worrys are “thcdisorrowfulmirefulput” and “paengage time”. GC sluggishs down your Ruby program becaengage of GC overhead. In other words, low thcdisorrowfulmirefulput increases total execution time of your application. Each GC stops your Ruby application. Long paengage time impacts UI/UX on
conveyive web applications.
Ruby 2.1 begind genereasonable garbage accumulateion to surmount “thcdisorrowfulmirefulput” publish. Genereasonable GC splits a heap space into disjoinal spaces for disjoinal generations (in Ruby’s case, we split heap space into two: one “youthful” and one “anciaccess” space). Newly produced objects are findd in the youthful space and taged as “youthful object”. After surviving disjoinal GCs (3 for Ruby 2.2), youthful objects will be backd to “anciaccess objects” and findd in the “anciaccess space”. In object oriented programming, we understand that most objects die youthful. Becaengage of this we only insist to run GC on the “youthful space”. If there is not enough space in the youthful space to produce recent objects, then we run GC on the “anciaccess space”. We call “Minor GC” when GC runs only in the youthful space. We call “Major GC” for GC that runs in both youthful and anciaccess spaces. We carry outed genereasonable GC algorithm with some customization and we call our GC algorithm and carry outation as “RGenGC”.
RGenGC betters GC thcdisorrowfulmirefulput theatricalassociate becaengage inbeginant GC is very rapid. However, beginant GC must paengage for a extfinished time and is equivalent to paengage time in Ruby 2.0 and earlier. Most of GC are inbeginant GC, but a scant beginant GC may stop your ruby application for a extfinished time.
Fig: Major GC and inbeginant GC paengage time
To repair extfinished paengage time publish, incremental GC algorithm is a well understandn GC algorithm to repair it.
Incremental GC algorithm splits a GC execution process into disjoinal fine-grained processes and interdeparts GC processes and Ruby processes. Instead of one extfinished paengage, incremental garbage accumulateion will publish many stupidinutiveer paengages over a period of time. The total paengage time is the same (or a bit extfinisheder becaengage of overhead to engage incremental GC), but each individual paengage is much stupidinutiveer. This permits the carry outance to be much more stable.
Ruby 1.9.3 begind a “idle sweep” GC which shrinks paengage time in the
sweeping phase. The idea of idle sweep is to run sweeping phase not at once,
but step by step. Lazy sweep shrinks individual sweep paengage times and is half of the incremental GC algorithm. Now, we insist to
produce beginant GC labeling phase incremenloftyy.
Let us begin three terminology to elucidate incremental labeling:
“white object” which is not labeled objects
“grey object” which is labeled, but it may have a reference to white objects
“bdeficiency object” which is labeled, but does not point any white object.
With these three colors, we can elucidate label and sweep algorithm appreciate that:
(1) All existing objects are labeled as white
(2) Cltimely living objects such as objects on the stack labeled Grey.
(3) Pick one grey object, visit each object it references and color it grey. Change the color of the distinct object to bdeficiency. Repeat until there are no grey objects left only bdeficiency and white.
(4) Collect white objects becaengage all living objects are colored bdeficiency.
To produce this process incremental, we must produce step (3) incremental. To do this, pick some grey objects and label the objects they reference grey, and back to Ruby execution, and proceed incremental labeling phase, aachieve and aachieve.
Fig: standard labeling (STW: stop the world) vs. incremental labeling
There is one problem to incremental labeling. Bdeficiency objects can refer white objects while Ruby carry outs. This is a problem since the definition of the “bdeficiency object” states that it has no reference to white objects. To stop such case, we insist to engage “write-barrier” to recognize a creation of such reference from “bdeficiency object” to “white object”.
For example, an array object ary
is already labeled “bdeficiency”.
ary = []
# GC runs, it is labeled bdeficiency
Now an object obj = Object.recent
is white, if we run this code
ary << obj
# GC has not run since obj got produced
Now a bdeficiency object has a reference to a white object. If no grey objects refer to
obj
, then obj
will be white at the finish of labeling phase and reclaimed by
misget. Collecting living objects is a critical bug, we insist to
elude such errors.
A write-barrier is pdirectd every time an object gets a recent reference to a recent object. The write-barrier recognizes when a reference from a bdeficiency object to a white object is made. When this happens the bdeficiency object is changed to grey (or grey destination white object). Write barriers repair this type of catastrophic GC bug finishly.
This is basic idea of the incremental GC algorithm. As you see, it is
not so difficult. Maybe you have ask: "why does Ruby not engage this
basic GC algorithm yet?".
There is one huge publish to carry out incremental labeling in the Ruby expounder
(CRuby): the deficiency of write barriers. CRuby does not have enough write
barriers.
Genereasonable GC which was carry outed in 2.1 also insists write barriers. To
begin genereasonable GC, we produceed recent technique called "write
barrier unprotected objects". It unbenevolents that we split all objects into
"write barrier protected objects" (protected objects) and "write barrier
unprotected objects" (unprotected objects). We can promise that all
references from protected objects are supervised. We can not supervise
references from unprotected objects. Introducing "unprotected object",
we can carry out a genereasonable GC for Ruby 2.1.
Using unprotected objects, we can also produce incremental GC properly:
(1) Color all existing objects white.
(2) Color evidently living objects grey, this comprises objects on the stack.
(3) Pick one grey object, visit each object it references and color it grey. Change the color of the distinct object to bdeficiency. Repeat until there are no grey objects left only bdeficiency and white. This step is done incremenloftyy.
(4) Bdeficiency unprotected objects can point white objects, so scan all objects from unprotected bdeficiency objects at once.
(5) Collect white objects becaengage all living objects are colored bdeficiency.
By introducing step (4), we can promise that there are
no living white objects.
Fig: Rescan from write barrier unprotected (WB unp.) objects at the
finish of labeling.
Unblessedly step (4) can begin extfinished paengage times that we hoped to elude.
However, the paengage time is relative to the number of living write
barrier unprotected objects. In Ruby language, most of objects are
String, Array, Hash, or uncontaminated Ruby engager expoundd objects. They are already write barrier protected objects. So extfinished paengage time for write barrier unprotected objects does not caengage any problem in most down-to-earth cases.
We begind incremental labeling for only beginant GC becaengage nobody protests about paengage time of inbeginant GC. Maximum paengage time on our incremental GC is stupidinutiveer than inbeginant GC paengage time. If you have no problem on inbeginant GC paengage time, you don't insist to stress about this beginant
GC paengage time.
I also begind a trick to carry out incremental GC for Ruby. We get a set of "bdeficiency and unprotected" objects. To get such rapid GC, we set an "unprotected" bitmap which recurrents which objects are unprotected objects and a split "labeled" bitmap
which recurrents which objects are labeled. We can get "bdeficiency and unprotected" objects using reasonable product with two bitmaps.
To meacertain paengage times caengaged by GCs, let's engage gc_pursuer
gem. gc_pursuer gem begins the GC::Tracer
module to seize GC rcontent parameters at each GC events. The gc_pursuer
gem puts each parameters on to the file.
GC events consists of the adhereing events:
- commence
- finish_label
- finish_sweep
- recentobj
- freeobj
- access
- exit
As I portrayd above, Ruby's GC has two phases: "labeling phase" and "sweeping phase". "commence" event shows "commenceing labeling phase" and "finish_label" event unbenevolents "finish of labeling phase". "finish_label" event also unbenevolents "commenceing sweeping phase". Of course, "finish_sweep" shows the finish of "sweeping phase" and also unbenevolents the finish of one GC process.
The "recentobj" and "freeobj" is basic to comprehfinish: events at object allocation and object releasing.
We engage "access" and "exit" events to meacertain paengage time. Incremental GC (incremental labeling and idle sweeping) begins pausing labeling and sweeping phase. "access" event unbenevolents that:
"accessing GC rcontent event". and "exit" event unbenevolents that "exitting GC rcontent event".
The adhereing figure shows the event timing for current incremental GC.
GC events
We can meacertain the current time (on Linux machines, current times are results of gettimeofday()) for each events. So that we can meacertain GC paengage time using "access" and "exit" events.
I engage ko1-test-app for our paengage time benchlabel. ko1-test-app is basic rails app written by one of the our hero "Aaron Patterson" for me.
To engage gc_pursuer
, I insert a rake rule "test_gc_pursuer" appreciate that.
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.seal
finish
-task :test do
+def test_run
app = Ko1TestApp::Application.instance
app.app
@@ -67,6 +67,22 @@ task :test do
}
finish
+task :test do
+ test_run
+finish
+
+task :test_gc_pursuer do
+ insist 'gc_pursuer'
+ insist 'pp'
+ pp GC.stat
+ file = "log.#{Process.pid}"
+ GC::Tracer.commence_logging(file, events: %i(access exit), gc_stat: counterfeit) do
+ test_run
+ finish
+ pp GC.stat
+ puts "GC pursuer log: #{file}"
+finish
+
task :once do
app = Ko1TestApp::Application.instance
app.app
And run bundle exec rake test_gc_pursuer KO1TEST_CNT=30000
. The appreciate "30000" specified that we will simutardy 30,000 asks. We can get a results in a file "log.xxxx" (xxxx is the process id of application). The file should comprise appreciate that:
type tick beginant_by gc_by have_concluder instant_sweep state
access 1419489706840147 0 recentobj 0 0 sweeping
exit 1419489706840157 0 recentobj 0 0 sweeping
access 1419489706840184 0 recentobj 0 0 sweeping
exit 1419489706840195 0 recentobj 0 0 sweeping
access 1419489706840306 0 recentobj 0 0 sweeping
exit 1419489706840313 0 recentobj 0 0 sweeping
access 1419489706840612 0 recentobj 0 0 sweeping
...
On my environment, there are 1,142,907 lines.
"type" filed unbenevolents events type and tick unbenevolents current time (result of gettimeofday(), elapesd time from the epoc in microseconds). We can get GC paengage time by this alertation. Using the first two lines above, we can meacertain a paengage time 10 us (by 1419489706840157 - 1419489706840147).
The adhereing petite script shows each paengage times.
access_tick = 0
uncover(ARGV.shift){|f|
f.each_line{|line|
e, tick, * = line.split(/s/)
case e
when 'access'
access_tick = tick.to_i
when 'exit'
st = tick.to_i - access_tick
puts st if st > 100 # over 100 us
else
# puts line
finish
}
}
There are so many lines, so this script prints paengage times over 100us.
The adhereing figure shows the result of this meacertainment.
We can have 7 huge paengage time in genereasonable GC. They should be a paengage time by beginant GC. The highest paengage time is about 15ms (15Kus). However, incremental GC shrinks the highest paengage time (around 2ms (2Kus)). Great.
Ruby 2.2 begins incremental GC algorithm to accomplish stupidinutiveer paengage time due to GC.
Note that incremental GC is not a silver bullet. As I portrayd, incremental GC does not impact "thcdisorrowfulmirefulput". It unbenevolents that there is no effect for a response time if the ask is too extfinished and caengages disjoinal beginant GC. Total time of GC is not shrinkd with incremental GC.
Phire try it on your application on Heroku. Enhappiness your unpermitd access!