Garbage Collection and Fibers

Hi,

I’ve been having an issue with my program running slow at points where
it really just shouldn’t. I’ve finally determined (I think) that the
culprit is actually garbage collection being called.

I want to have more control over GC. However, the problem is, I suspect
that later on it could take 1 second or longer each time it runs, and I
can’t have my program being blocked for that long. One option is that I
could put GC in a separate thread (I haven’t tested whether this will
cause stability issues). Another, I wondered, is whether or not GC
could be called in a fiber somehow so that its execution load can be
stretched out over 5 seconds or so as I deem appropriate?

Other things I’ll be doing will be seeing if I can find ways to reduce
the number of objects I am creating, but I suspect that I will need to
look at the above kinds of improvements even after this.

Thanks!

On Mon, 14 Jan 2013 00:48:09 +0100, Na Na [email protected] wrote:

cause stability issues). Another, I wondered, is whether or not GC
could be called in a fiber somehow so that its execution load can be
stretched out over 5 seconds or so as I deem appropriate?

Other things I’ll be doing will be seeing if I can find ways to reduce
the number of objects I am creating, but I suspect that I will need to
look at the above kinds of improvements even after this.

Thanks!

You can’t do any of that. Ruby has a stop-the-world mark-and-sweep
garbage collector, which means that every time GC is called everything
(all threads, fibers, whatever) has to stop executing while GC walks
all of your objects (starting from constants, global variables and
variables in active scopes) to mark them as used and then deallocates
the rest.

This process can’t be paused or executed partially, nor can any Ruby
code run while it’s in progress, because modifying the object tree while
GC is traversing it could cause Ruby to miss some of your objects, and
mistakenly deallocate them when they’re still used.

Only thing you can do is call GC.start when you want the GC to happen.

Aha. I realised that running it in a separate thread or fiber would
mean it would miss some objects, but I was thinking that would mean it
may not deallocate them when it should, which I didn’t care so much
about. I didn’t think that it might cause some to be deallocated when
they shouldn’t!

So I suppose my options are:

  • Call GC only when suits me, and put up with the slowness as a part of
    life
  • Reduce the number of objects I use
  • Tweak the GC settings so that it’s needed to be called less frequently
    (but I suspect this will just postpone my issues)
  • Try ruby 2.0’s GC
  • Use something other than ruby for my app (nowhere near an ideal
    option!)

Subject: Re: Garbage Collection and Fibers
Date: Mon 14 Jan 13 09:43:20AM +0900

Quoting Na Na ([email protected]):

  • Reduce the number of objects I use

I think this is the wisest option at your disposal. When a GC’ed
language is embraced, you obtain great advantages, but at the cost of
some compromises.

You will of course need to refactor, but the result will be better,
and you will learn a lot in the process.

If you find yourself in the situation you describe, it means that you
are constantly creating and throwing away lots of objects. Think about
what can be reused. Divide your object logic between stuff that you
often need and stuff you rarely use. Stuff used often is better
created at beginning and never thrown away.

What I generally do is create a main class for my application. The
first code I write is its initialize method, which creates and assigns
to instance variables the material I am supposed to always have
available.

  • Use something other than ruby for my app (nowhere near an ideal
    option!)

NOOOOOOOOOOOO!!! :sunglasses:

Carlo

On Mon, Jan 14, 2013 at 8:12 AM, Florian G. [email protected]
wrote:

On Jan 14, 2013, at 1:43 AM, Na Na [email protected] wrote:

  • Reduce the number of objects I use

I would certainly try to find out why it’s slow or GC has so much to
do. What type of application are we talking about? Is it really the
number of garbage to collect which makes things slow or maybe low
memory leading to paging (something usually not well taken by programs
with GC since the GC needs to look at a lot of memory during its
work).

Don’t forget option X: If you don’t need extensions that are fundamentally
incompatible, have a look at running the whole thing on JRuby. It uses the
JVMs GC, which is much better and doesn’t suffer from stop-the-world
issues as much. It is also very compatible with MRI.

I thought that was option J. :slight_smile:

Additional benefit: OP gets real threads without GIL.

Kind regards

robert

I have some silly preference against JRuby. For some reason I don’t
like java, and it’s a purely aesthetic dislike. Not even anything to do
with the look of the language!

I did test JRuby out though. I’m using a perlin noise gem that’s been
built as a c extension, and it’s quite fast. Unfortunately it didn’t
work with jruby, and a jruby compatible gem was significantly slower. I
need this speed, and don’t want to rewrite it myself in java (if that
turns out to be fast enough anyway).

I have heard that GC is improved in ruby 2.0.

You will of course need to refactor, but the result will be better,
and you will learn a lot in the process.

This I think will be very true! I need to learn how to profile the
Garbage Collector, and find out the hotspots where I’m doing a lot of
objection creation and deletion.

I have heard a rumour that using for-in rather than .each involves more
object creation and deletion, but I don’t know if that’s true. It will
be one of the things I’ll be testing to see if it helps improve
performance.

Is it really the
number of garbage to collect which makes things slow or maybe low
memory leading to paging

I think your suggestion here is that system memory is running out, so
garbage collection is using a hard drive based swap file. If that’s
what you mean, I suspect that’s not the issue since I have 8GB memory on
my system, my app seems to use < 500MB right now, and I don’t think I
have enough unrelated loaded apps. I’m on mac os x and ‘free’ doesn’t
seem to work to tell me memory usage, so I can’t be sure :slight_smile: I will keep
this in mind though.

On Jan 14, 2013, at 1:43 AM, Na Na [email protected] wrote:

  • Tweak the GC settings so that it’s needed to be called less frequently
    (but I suspect this will just postpone my issues)
  • Try ruby 2.0’s GC
  • Use something other than ruby for my app (nowhere near an ideal
    option!)

Don’t forget option X: If you don’t need extensions that are
fundamentally
incompatible, have a look at running the whole thing on JRuby. It uses
the
JVMs GC, which is much better and doesn’t suffer from stop-the-world
issues as much. It is also very compatible with MRI.

Regards,
Florian

At
http://www.web-l.nl/posts/15-tuning-ruby-s-garbage-collector-with-rvm-and-passenger
under option 2, the discussion led me to believe that the writer thought
changing those from for-in to each would help.

I did some reading for a description on the difference between those
two, and it did indeed only mention scope as the difference. I couldn’t
understand why that would lead to additional object creation.

On Mon, Jan 14, 2013 at 2:54 PM, Na Na [email protected] wrote:

I have heard a rumour that using for-in rather than .each involves more
object creation and deletion, but I don’t know if that’s true. It will
be one of the things I’ll be testing to see if it helps improve
performance.

That isn’t true. The only difference between a for loop and an each
loop is the different scoping. I can’t see how iterating would create
more objects either way. Can you share a source of that “rumor”?

Kind regards

robert

On Mon, Jan 14, 2013 at 5:54 AM, Na Na [email protected] wrote:

I did test JRuby out though. I’m using a perlin noise gem that’s been
built as a c extension, and it’s quite fast. Unfortunately it didn’t
work with jruby, and a jruby compatible gem was significantly slower.

Just curious, what gem were you using for this? There are a number of
things that can affect the performance of JRuby gems (e.g. binding to
poor-quality Java code, using JI instead of writing a Java extension)

It looks like you probably want a gem that wraps this:

http://code.j3d.org/javadoc/org/j3d/texture/procedural/PerlinNoiseGenerator.html

I think you will have a lot easier time finding a fast way to generate
Perlin noise on JRuby than you will finding better garbage collection
solutions on MRI

I think it was this gem: perlin_noise | RubyGems.org | your community gem host

I think you will have a lot easier time finding a fast way to generate
Perlin noise on JRuby than you will finding better garbage collection
solutions on MRI

:frowning:

Why is it that MRI is inferior to JRuby? You’d think something coded in
C would run faster.

Edit: I can’t remember which perlin noise gem I used with JRuby. But
the one I used with MRI was written in C, so very fast.

On Tue, 15 Jan 2013 01:11:28 +0100, Na Na [email protected] wrote:

I think you will have a lot easier time finding a fast way to generate
Perlin noise on JRuby than you will finding better garbage collection
solutions on MRI

:frowning:

Why is it that MRI is inferior to JRuby? You’d think something coded in
C would run faster.

Because Java’s GC (which JRuby depends on), as well as its entire
runtime, has had thousands of man-hours put into optimising it.

Compared to Java, MRI was written by a couple of guys in their free
time. :wink:

On Mon, Jan 14, 2013 at 11:54 PM, Na Na [email protected] wrote:

At

http://www.web-l.nl/posts/15-tuning-ruby-s-garbage-collector-with-rvm-and-passenger

under option 2, the discussion led me to believe that the writer thought
changing those from for-in to each would help.

First of all that’s the opposite of what you wrote initially. Then
the author doesn’t really track down the “relief” to the different
looping style (he also changed to “render @model”).

I did some reading for a description on the difference between those
two, and it did indeed only mention scope as the difference. I couldn’t
understand why that would lead to additional object creation.

Can you show a use case you have in mind? The only memory relevant
issue I can see would be allocating and releasing of additional local
variables - but not objects.

Cheers

robert

That’s a pure Ruby gem so it’s not surprising it’s slower than the C
version.

If I were you and I really wanted to give JRuby an honest shot, I’d try
using the Perlin noise function from j3d:

PerlinNoiseGenerator (j3d.org Code Library)

Is there a quick way to try this out? I’m trying with irb:
require ‘java’

java.org.j3d.texture.procedural.PerlinNoiseGenerator

But that’s replying:
NameError: missing class or uppercase package name
(java.org.j3d.texture.procedural.PerlinNoiseGenerator') from org/jruby/javasupport/JavaUtilities.java:54:in get_proxy_or_package_under_package’
from
file:/usr/local/rvm/rubies/jruby-1.7.1/lib/jruby.jar!/jruby/java/java_package_module_template.rb:10:in
method_missing' from (irb):14:in evaluate’
from org/jruby/RubyKernel.java:1066:in eval' from org/jruby/RubyKernel.java:1392:in loop’
from org/jruby/RubyKernel.java:1174:in catch' from org/jruby/RubyKernel.java:1174:in catch’
from /usr/local/rvm/rubies/jruby-1.7.1/bin/irb:13:in `(root)’

Whereas another quick test trying something else
works. I am completely unfamiliar with java. Is it easy to give a
quick example of how to test this PerlinNoiseGenerator inline?

Thanks

On Mon, Jan 14, 2013 at 4:15 PM, Na Na [email protected] wrote:

I think it was this gem: perlin_noise | RubyGems.org | your community gem host

That’s a pure Ruby gem so it’s not surprising it’s slower than the C
version.

If I were you and I really wanted to give JRuby an honest shot, I’d try
using the Perlin noise function from j3d:

http://code.j3d.org/javadoc/org/j3d/texture/procedural/PerlinNoiseGenerator.html

On Wed, Jan 16, 2013 at 8:29 AM, Na Na [email protected] wrote:

Is there a quick way to try this out? I’m trying with irb:
require ‘java’

java.org.j3d.texture.procedural.PerlinNoiseGenerator

You shouldn’t have “java” in front of this. You might reference
classes using Java::some.package.SomeClass but normally the packages
“java”, “javax”, “org”, and “com” have top-level access.

I was able to get a PerlinNoiseGenerator constructed:

irb(main):001:0> puts JRUBY_VERSION
1.7.3.dev
=> nil
irb(main):002:0> require ‘jars/j3d-org-all_1.1.0.jar’
=> true
irb(main):003:0> pg =
org.j3d.texture.procedural.PerlinNoiseGenerator.new
=> #Java::OrgJ3dTextureProcedural::PerlinNoiseGenerator:0x2eed7d11

  • Charlie

On Mon, Jan 14, 2013 at 6:15 PM, Na Na [email protected] wrote:

I think it was this gem: perlin_noise | RubyGems.org | your community gem host

I’d like to see the benchmark you ran that was slower on JRuby with
this gem. As Tony said, we would not yet expect pure Ruby to compete
with C, but I’d like to see if we can get it closer.

  • Charlie

Just curious, what gem were you using for this? There are a number of
things that can affect the performance of JRuby gems (e.g. binding to
poor-quality Java code, using JI instead of writing a Java extension)

If I use ‘require’ to bring in a jar file, and run the code from that,
is that using JI instead of Java extension? I’m not quite sure on the
distinction and the implication.

I’m having great difficulty finding a suitable perlin/simplex noise
replacement. But that might be something I’ll just have to put up with
for the advantages of jruby, if indeed those advantages are worth it :slight_smile:

Charles Nutter wrote in post #1093064:

I’d like to see the benchmark you ran that was slower on JRuby with
this gem. As Tony said, we would not yet expect pure Ruby to compete
with C, but I’d like to see if we can get it closer.

  • Charlie

I actually haven’t given the full story, here. The “perlin” noise
c-extension was actually using simplex noise, which is faster than
perlin noise. So the extension had two advantages over the gem I
listed:
a) Simplex noise (faster than perlin)
b) Coded in c

So it may be a bit hard to compare them.

I didn’t actually create benchmarks, unfortunately. There was a task I
was performing within the program that ran noticeably slower.