-
-
Notifications
You must be signed in to change notification settings - Fork 925
Permalink
Choose a base ref
{{ refName }}
default
Choose a head ref
{{ refName }}
default
Comparing changes
Choose two branches to see what’s changed or to start a new pull request.
If you need to, you can also or
learn more about diff comparisons.
Open a pull request
Create a new pull request by comparing changes across two branches. If you need to, you can also .
Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 32f213b20741
Could not load branches
Nothing to show
Loading
Could not load tags
Nothing to show
{{ refName }}
default
Loading
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 903728b770b1
Could not load branches
Nothing to show
Loading
Could not load tags
Nothing to show
{{ refName }}
default
Loading
- 2 commits
- 1 file changed
- 2 contributors
Commits on Apr 30, 2017
-
Fix wrong method lookup cache used when class is prepended
While benchmarking a @Talkdesk JRuby application during our recent hackaton, I noticed quite a lot of thread contention in a workload where there should be no shared data. Stack traces showed that there was contention while performing writes to the method lookup cache: ``` java.lang.Thread.State: BLOCKED (on object monitor) at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1027) - waiting to lock <0x00000000e1565058> (a java.util.concurrent.ConcurrentHashMap$Node) at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006) at org.jruby.RubyModule.addToCache(RubyModule.java:1467) at org.jruby.RubyModule.searchWithCache(RubyModule.java:1315) at org.jruby.RubyModule.searchWithCache(RubyModule.java:1269) at org.jruby.RubyModule.searchMethod(RubyModule.java:1258) at org.jruby.RubyModule.respondsToMethod(RubyModule.java:1829) ``` I ended up instrumenting the JRuby code and noticed that this kept happening (on this workload) for calls to `Array.respond_to?(:to_ary)` which happened quite often and never seemed to be cached. But why were threads repeatedly failing to lookup `#to_ary`, saving it on the cache, and then not finding it the next time around? Every instance of the `RubyModule` class keeps a method lookup cache in the `myCachedMethods` and normally reads and writes to it. But additionally, every `RubyModule` instance keeps a reference called `methodLocation` to the object where it keeps its methods. Usually, `methodLocation` is set to `this` (e.g. the `RubyModule` stores its own methods), but in some cases, e.g. when a class is prepended, `methodLocation` changes, and points elsewhere. The problem here is that when looking up on the `myCachedMethods`, the `cacheHit` method always checks the local `myCachedMethods` reference, while the `addToCache` writes to the cache inside the `methodLocation` reference. This means that normally the method cache works fine as `this.myCachedMethods` and `this.methodLocation.myCachedMethods` are the same, but when `methodLocation` changes, the cache reads will never find the correct cached method. As a fix, I updated the `cacheHit` method to also read from `this.methodLocation.myCachedMethods`. Included below are my test cases and results from executing them. Thanks go to @Talkdesk for letting me work on making JRuby kick even more ass. Single-threaded performance impact test-case: ```ruby require 'benchmark/ips' module Foo; end mode = ARGV.first if mode == 'prepend' Array.prepend(Foo) elsif mode == 'no-prepend' # do nothing else fail 'no mode supplied' end Benchmark.ips do |benchmark| benchmark.time = 15 benchmark.warmup = 15 benchmark.report(mode) { [1, 2, 3].respond_to?(:to_ary) } benchmark.compare! end ``` Before fix: ``` $ jruby singlethreaded-bug-bench.rb no-prepend Warming up -------------------------------------- no-prepend 265.802k i/100ms Calculating ------------------------------------- no-prepend 14.619M (± 5.1%) i/s - 218.489M in 14.993450s $ jruby singlethreaded-bug-bench.rb prepend Warming up -------------------------------------- prepend 222.863k i/100ms Calculating ------------------------------------- prepend 6.879M (± 3.1%) i/s - 103.186M in 15.017267s ``` After fix: ``` $ jruby singlethreaded-bug-bench.rb no-prepend Warming up -------------------------------------- no-prepend 272.227k i/100ms Calculating ------------------------------------- no-prepend 14.229M (± 4.1%) i/s - 212.882M in 14.990837s $ jruby singlethreaded-bug-bench.rb prepend Warming up -------------------------------------- prepend 271.436k i/100ms Calculating ------------------------------------- prepend 14.095M (± 4.2%) i/s - 210.906M in 14.996117s ``` Multi-threaded contention test case: ```ruby module Foo; end mode = ARGV.first if mode == 'prepend' Array.prepend(Foo) elsif mode == 'no-prepend' # do nothing else fail 'no mode supplied' end Integer(ENV['THREADS'] || 16).times do Thread.new { [1, 2, 3].respond_to?(:to_ary) while true } end sleep ``` (See VisualVM screenshots attached to PR for results).
Configuration menu - View commit details
-
Copy full SHA for 8420b3e - Browse repository at this point
Copy the full SHA 8420b3eView commit details
Commits on May 3, 2017
-
Merge pull request #4581 from ivoanjo/fix-wrong-method-lookup-cache
Fix wrong method lookup cache used when class is prepended
Configuration menu - View commit details
-
Copy full SHA for 903728b - Browse repository at this point
Copy the full SHA 903728bView commit details
There are no files selected for viewing