Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolv.getaddress half the speed of MRI #3241

Open
nirvdrum opened this issue Aug 10, 2015 · 4 comments
Open

Resolv.getaddress half the speed of MRI #3241

nirvdrum opened this issue Aug 10, 2015 · 4 comments

Comments

@nirvdrum
Copy link
Contributor

I was recently looking at doing some custom name resolution and ended up investigating the various options within Ruby. It looks like in MRI, IPSocket.getaddress makes a native call to getaddrinfo and this can be slow, leading some Rubyists to conclude that using the resolv library is a better option. In JRuby, the native call ends up being much faster (although this may just be due caching) and the resolv library (pure Ruby) resolves at roughly half the speed of MRI.

The benchmark:

require 'benchmark/ips'
require 'socket'
require 'resolv'

Benchmark.ips do |x|
  x.report("native") { IPSocket.getaddress("localhost") } 
  x.report("ruby") { Resolv.getaddress("localhost") }
end

The results:

MRI:

ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Calculating -------------------------------------
              native     4.453k i/100ms
                ruby    35.105k i/100ms
-------------------------------------------------
              native     52.098k (± 3.0%) i/s -    262.727k
                ruby    497.804k (± 5.5%) i/s -      2.492M

JRuby master:

jruby 9.0.1.0-SNAPSHOT (2.2.2) 2015-08-07 2939c73 Java HotSpot(TM) 64-Bit Server VM 25.51-b03 on 1.8.0_51-b16 +jit [linux-amd64]
Calculating -------------------------------------
              native    60.323k i/100ms
                ruby    13.003k i/100ms
-------------------------------------------------
              native      2.565M (± 8.9%) i/s -     12.668M
                ruby    202.637k (± 4.2%) i/s -      1.014M

JRuby 1.7.21:

jruby 1.7.21 (1.9.3p551) 2015-07-07 a741a82 on Java HotSpot(TM) 64-Bit Server VM 1.8.0_51-b16 +jit [linux-amd64]
Calculating -------------------------------------
              native    88.987k i/100ms
                ruby    15.782k i/100ms
-------------------------------------------------
              native      2.273M (± 6.2%) i/s -     11.390M
                ruby    227.596k (± 4.2%) i/s -      1.136M
@nirvdrum
Copy link
Contributor Author

There's a lot of variance in the numbers, particularly around the native calls. I think getaddrinfo depends on what else is going on with the system. The Ruby results are fairly stable, however. Including JRuby 1.7.21 slightly beating out master (9.0.0.0). Both are roughly half of what MRI is.

@nirvdrum
Copy link
Contributor Author

I should note that I picked "localhost" in the benchmark because it should be in /etc/hosts. The point was to show the speed of the Resolv::Hosts resolver, which is fairly simplistic.

@headius
Copy link
Member

headius commented Aug 19, 2015

There's a great deal of allocation happening here, which likely has something to do with the overhead.

Here's an allocation trace of the top items:

          percent          live          alloc'ed  stack class
 rank   self  accum     bytes objs     bytes  objs trace name
    1  5.56%  5.56%   2184960 13656   8094080 50588 447864 int[]
    2  5.28% 10.83%   2075712 13656   7689376 50588 447860 org.joni.ByteCodeMachine
    3  3.06% 13.89%   1201728 13656   4451744 50588 447863 int[]
    4  3.06% 16.95%   1201728 13656   4451744 50588 447862 int[]
    5  2.17% 19.12%    852784 6932   1728504 35612 417545 char[]
    6  1.94% 21.06%    764736 13656   2844856 50801 444101 org.jruby.runtime.Binding
    7  1.94% 23.01%    764736 13656   2832928 50588 447868 org.jruby.runtime.Binding
    8  1.94% 24.95%    764736 13656   2832928 50588 447872 org.jruby.runtime.Binding
    9  1.94% 26.89%    764736 13656   2758840 49265 455800 org.jruby.runtime.Binding
   10  1.73% 28.63%    681200 12708    681200 12708 300000 char[]
   11  1.67% 30.29%    655536 13657   2364720 49265 455804 org.jruby.runtime.Frame
   12  1.67% 31.96%    655488 13656   2428176 50587 447891 org.jruby.runtime.Frame
   13  1.67% 33.63%    655488 13656   2438400 50800 444197 org.jruby.runtime.Frame
   14  1.39% 35.02%    546240 13656   2023520 50588 447865 org.jruby.RubyRegexp$SearchMatchTask
   15  1.39% 36.41%    546240 13656   2023520 50588 447873 org.jruby.runtime.Block
   16  1.39% 37.79%    546240 13656   2023520 50588 447858 org.jruby.runtime.scope.TwoVarDynamicScope
   17  1.39% 39.18%    546240 13656   2037400 50935 441777 org.jruby.RubyString
   18  1.39% 40.57%    546240 13656   2023520 50588 447869 org.jruby.runtime.Block
   19  1.39% 41.96%    546240 13656   1970600 49265 455801 org.jruby.runtime.Block
   20  1.39% 43.35%    546240 13656   2032040 50801 444102 org.jruby.runtime.Block
   21  1.39% 44.74%    546200 13655   2023520 50588 447856 org.jruby.ir.runtime.IRReturnJump
   22  1.11% 45.85%    436992 13656   1625632 50801 444100 org.jruby.runtime.scope.OneVarDynamicScope
   23  1.11% 46.96%    436992 13656   1618816 50588 447861 org.joni.Region
   24  1.11% 48.07%    436992 13656   1576480 49265 455797 org.jruby.runtime.scope.NoVarsDynamicScope
   25  1.11% 49.19%    436992 13656   1616128 50504 447979 org.jruby.runtime.scope.OneVarDynamicScope
   26  1.11% 50.30%    436992 13656   1576480 49265 455803 org.jruby.runtime.scope.NoVarsDynamicScope
   27  1.11% 51.41%    436992 13656   1618816 50588 447871 org.jruby.runtime.scope.OneVarDynamicScope
   28  0.83% 52.24%    327904 10247    327904 10247 349979 java.util.HashMap$Entry
   29  0.83% 53.08%    327744 13656   1212072 50503 447977 org.jruby.runtime.builtin.IRubyObject[]
   30  0.83% 53.91%    327744 13656   1214112 50588 447870 org.jruby.runtime.builtin.IRubyObject[]
   31  0.83% 54.74%    327744 13656   1214112 50588 447859 org.jruby.runtime.builtin.IRubyObject[]
   32  0.83% 55.58%    327744 13656   1182360 49265 455802 org.jruby.runtime.builtin.IRubyObject[]
   33  0.83% 56.41%    327744 13656   1212096 50504 447978 org.jruby.runtime.builtin.IRubyObject[]

Most of this appears to be happening in Resolv's each_address method, which does a regex match and some nested blocks that recurse.

The IRubyObject[] are likely from passing arguments to these blocks; Unlike 1.7, 9k never does "specific arity" dispatch for blocks, and this is a known area to improve. The Binding, *DynamicScope and Frame instances are similarly managing heap state for these blocks; we may be allocating more of these than necessary compared to 1.7.

The top items like int[] and ByteCodeMachine are from repeatedly allocating a new bytecode engine for processing the AddressRegex at the top of each_address. There may be an opportunity here to pool these instances rather than creating them new for every call to the regex.

@headius
Copy link
Member

headius commented Aug 19, 2015

Numbers across various versions with and without indy show that indy helps us quite a bit here:

MRI 2.2: 216k
JRuby 9k no indy: 140k
JRuby 9k + indy: 214k
JRuby 1.7 no indy: 157k
JRuby 1.7 + indy: 228k

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants