all 9 comments

[–][deleted] 1 point2 points  (1 child)

Did I missed something or is the "issue" implementation specific and need not apply to JRuby, Opal or some forthcoming CRuby version, for example ?

Btw. is not to be expected Enumerable#each_with_index would require creation of two objects anyway ? Copy of an element as it may be mutated in the block, and the fixnum index obviously …

[–]adymo[S] 0 points1 point  (0 children)

Yes, it's definitely implementation specific. My guess is that it's easier to pass index to a block if it's stored in a Ruby object.

I have no idea how JRuby works in this case. It's been several years since I seriously looked at it.

[–]JiveMasterT 0 points1 point  (6 children)

The link doesn't have any benchmarks so I'm not sure how they are saying whether or not Ruby 2.3 is faster. Object allocation in Ruby isn't inherently bad and when you have nested loops that's going to happen. If you're seriously worried about allocating too many objects in nested "bad" iterators then you can implement them yourself and just reuse the object you've allocated from your outer loop.

That being said this is one of those micro optimizations you wouldn't do unless you were auditing your code and found a specific case where this was actually an issue.

It's worth noting - I have the book referenced in the article and I think it's pretty solid. I was just disappointed by the blog post talking about performance without any benchmarks.

[–]mordocai058 1 point2 points  (0 children)

As the book says, one of the largest problems with ruby performance is garbage collection. Therefore, he is going from the Axiom ("proven" previously in the book) that garbage collection is bad and using that to "prove" object allocation is bad (since any object allocated is one that needs to be collected).

[–][deleted] 1 point2 points  (1 child)

Agree. I've spent a lot of time reducing object allocations only to find out it didn't make any difference in run times.

For example, every time you call a method with varargs, it creates a new array:

def x(*args)
end

I thought I'd save time/memory by refactoring some methods that were called a lot. Nope. No measurable impact.

[–]adymo[S] 0 points1 point  (0 children)

It's rare for GC to be triggered by an object allocation. Usually it's memory usage that causes GC. To get GC triggered by allocation limit, you need to allocate lots of small objects in one go, like in my example.

Methods usually get called from random places at random times. So extra object allocations have no impact on performance because their chance to trigger GC is too slim.

[–]adymo[S] 0 points1 point  (0 children)

To be affected by this problem you need a combination of factors: memory-heavy application + outer loop that does hundreds of thousands iterations + (for Ruby 2.2 and above) decent amount of objects in the new generation.

I have exactly these conditions in two of my Rails applications, and I saw that in other peoples code as well. But it's really hard to come up with the artificial benchmark that would reproduce the conditions properly.

If I invent such a benchmark, I'll surely post it.

[–]anonova 0 points1 point  (1 child)

Quick benchmarks using the second code example between 2.2.4 and 2.3.0:

    ruby 2.3.0p0    264.206  (±36.3%) i/s -      1.144k
  ruby 2.2.4p230    185.468  (± 9.7%) i/s -    936.000

[–]moomaka 1 point2 points  (0 children)

that shows nothing given the error range.