Ruby Forum Ruby-core > Performance on method dispatch for methods defined via define_method

Posted by Robert Dober (Guest)
on 22.04.2008 17:40
(Received via mailing list)
Hi

to my dispair (because it makes traits slow) method lookup is about
50% slower for methods defined via define_method vs. def.
If this is completely stupid a question just say so and sorry for
having wasted your time, but here I go.

Would there be any chance to get the same speed for method
lookup/invocation for methods defined via define_method?
Right now we have this:

647/147 > cat dispatch1.rb && ruby1.9 dispatch1.rb
# vim: sw=2 ts=2 ft=ruby expandtab tw=0 nu syn=on:
# file: dispatch1.rb

require 'benchmark'

module M
  def a; 42 end
  define_method :b do 42 end
  def c; 42 end
end

o = Object.new.extend M

N=1_000_000

Benchmark::bmbm do |bm|
  bm.report "method by def" do
    N.times do
      o.a
    end
  end
  bm.report "define_method" do
    N.times do
      o.b
    end
  end
  bm.report "def but after" do
    N.times do
      o.a
    end
  end
end
Rehearsal -------------------------------------------------
method by def   0.220000   0.000000   0.220000 (  0.220175)
define_method   0.320000   0.000000   0.320000 (  0.327819)
def but after   0.210000   0.000000   0.210000 (  0.221985)
---------------------------------------- total: 0.750000sec

                    user     system      total        real
method by def   0.210000   0.000000   0.210000 (  0.218688)
define_method   0.310000   0.000000   0.310000 (  0.326463)
def but after   0.200000   0.000000   0.200000 (  0.222299)

----------------------------------------------------------------------------------------------------------------------------------------------------




Thx in advance
Robert
--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
Posted by Paul Brannan (cout)
on 22.04.2008 20:48
(Received via mailing list)
On Wed, Apr 23, 2008 at 12:39:29AM +0900, Robert Dober wrote:
> to my dispair (because it makes traits slow) method lookup is about
> 50% slower for methods defined via define_method vs. def.
> If this is completely stupid a question just say so and sorry for
> having wasted your time, but here I go.

Why do you believe it is method lookup that is slower?  Your benchmark
tests both lookup and dispatch (I say this because I suspect it is the
dispatch that is slow).

> Would there be any chance to get the same speed for method
> lookup/invocation for methods defined via define_method?
> Right now we have this:

Method dispatch to a method defined with define_method is not much less
than the cost of calling a proc, which is essentially what's happening
under the hood:

[pbrannan@zaphod tmp]$ cat test.rb
require 'benchmark'

N=1_000_000

Benchmark::bmbm do |bm|
  p = proc { }
  o = Object.new
  osc = class << o; self; end
  osc.instance_eval {
    define_method(:foo, &p)
  }

  bm.report "define_method" do
    N.times { o.foo }
  end
  bm.report "proc.call" do
    N.times { p.call }
  end
end
[pbrannan@zaphod tmp]$ ruby1.9 -v test.rb
ruby 1.9.0 (2008-03-20 revision 0) [i686-linux]
Rehearsal -------------------------------------------------
define_method   1.090000   0.000000   1.090000 (  1.095923)
proc.call       1.210000   0.000000   1.210000 (  1.218726)
---------------------------------------- total: 2.300000sec

                    user     system      total        real
define_method   1.080000   0.020000   1.100000 (  1.106690)
proc.call       1.210000   0.000000   1.210000 (  1.219874)

To make methods defined with define_method faster, one would need to
make proc invocation faster.  I don't see any low-hanging fruit here,
though.  It's already much faster than 1.8:

[pbrannan@zaphod tmp]$ ruby -v test.rb
ruby 1.8.6 (2007-09-24 patchlevel 111) [i686-linux]
Rehearsal -------------------------------------------------
define_method   3.190000   0.000000   3.190000 (  3.196841)
proc.call       3.250000   0.010000   3.260000 (  3.255551)
---------------------------------------- total: 6.450000sec

                    user     system      total        real
define_method   3.210000   0.000000   3.210000 (  3.210368)
proc.call       3.280000   0.000000   3.280000 (  3.284931)

Paul
Posted by Robert Dober (Guest)
on 22.04.2008 21:04
(Received via mailing list)
On Tue, Apr 22, 2008 at 8:46 PM, Paul Brannan <pbrannan@atdesk.com> 
wrote:
> On Wed, Apr 23, 2008 at 12:39:29AM +0900, Robert Dober wrote:
>  > to my dispair (because it makes traits slow) method lookup is about
>  > 50% slower for methods defined via define_method vs. def.
>  > If this is completely stupid a question just say so and sorry for
>  > having wasted your time, but here I go.
>
>  Why do you believe it is method lookup that is slower?  Your benchmark
>  tests both lookup and dispatch (I say this because I suspect it is the
>  dispatch that is slow).
Completely correct forgive my sloppy language here I was preciser below.
>
>
>  > Would there be any chance to get the same speed for method
>  > lookup/invocation for methods defined via define_method?
>  > Right now we have this:
>
>  Method dispatch to a method defined with define_method is not much less
>  than the cost of calling a proc, which is essentially what's happening
>  under the hood:
>
Well spotted, I was thinking about this for some time and of course
only *after* posting this I guess I had an idea of what is going on,
as you said it is the proc that is called for methods defined with
define_method and this is slower.
However, if and only if, the block in the define_method does not use
the closure we could really inline the block as the code of a method
defined with def.
Can this be determined at compile time? I have a strong feeling that
yes, well I guess that might be an ultimate optimization item,
hopefully.

E.g.
module M
  x = 42
  define_method :a do x end # Cannot be inlined
  define_method :b do some_call; @x end # Can be inlined as if def were 
used
end

Did I miss something else?

Cheers
Robert




--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
Posted by David A. Black (Guest)
on 22.04.2008 22:45
(Received via mailing list)
Hi --

On Wed, 23 Apr 2008, Robert Dober wrote:

> Completely correct forgive my sloppy language here I was preciser below.
> Well spotted, I was thinking about this for some time and of course
> E.g.
> module M
>  x = 42
>  define_method :a do x end # Cannot be inlined
>  define_method :b do some_call; @x end # Can be inlined as if def were used
> end
>
> Did I miss something else?

class C
   x = 100
   define_method(:a) { x }
   define_method(:b){ puts eval(gets.chomp)}
end


David
Posted by Robert Dober (Guest)
on 23.04.2008 00:40
(Received via mailing list)
On Tue, Apr 22, 2008 at 10:44 PM, David A. Black <dblack@rubypal.com> 
wrote:

>
>  class C
>   x = 100
>   define_method(:a) { x }
>   define_method(:b){ puts eval(gets.chomp)}
>  end
>
>
>  David
David I am afraid I do not get the point,
you just gave another example of a method that cannot be "inlined"
because of the
eval, but that is easily detectable during compile time.
I would need an example where the binding is needed and this fact
cannot be detected during compilation!

Cheers
Robert
--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
Posted by Robert Dober (Guest)
on 23.04.2008 00:41
(Received via mailing list)
>  I would need an example where the binding is needed and this fact
Sorry: "binding".sub("binding", "closure")
Posted by David A. Black (Guest)
on 23.04.2008 00:49
(Received via mailing list)
Hi --

On Wed, 23 Apr 2008, Robert Dober wrote:

>>  David
> David I am afraid I do not get the point,
> you just gave another example of a method that cannot be "inlined"
> because of the
> eval, but that is easily detectable during compile time.
> I would need an example where the closure is needed and this fact
> cannot be detected during compilation!

   define_method(:c) { puts send([101, 118, 97, 108].pack("C*"),
   gets.chomp) }

:-)

I don't know... it seems to me that determining statically whether or
not a block will refer back to its context would be really, really
difficult, at the very least.


David
Posted by Joel VanderWerf (Guest)
on 23.04.2008 01:03
(Received via mailing list)
Robert Dober wrote:
> David I am afraid I do not get the point,
> you just gave another example of a method that cannot be "inlined"
> because of the
> eval, but that is easily detectable during compile time.
> I would need an example where the binding is needed and this fact
> cannot be detected during compilation!

Maybe David's point was that gets.chomp might return "x".
Posted by Robert Dober (Guest)
on 23.04.2008 01:54
(Received via mailing list)
On Wed, Apr 23, 2008 at 12:48 AM, David A. Black <dblack@rubypal.com> 
wrote:

>
>   define_method(:c) { puts send([101, 118, 97, 108].pack("C*"),
>   gets.chomp) }
>
>  :-)
>
>  I don't know... it seems to me that determining statically whether or
>  not a block will refer back to its context would be really, really
>  difficult, at the very least.

Not at all, David, at least not from your examples, it might follow
this simple strategy
Whenever a reference to the closure is made, --> return :do_not_inline
Whenever metaprogramming like eval or send is done
    can I determine the parameters of send/eval e.g. simple literals
--> evaluate them and react accordingly
    else --> return :do_not_inline
return --> :inline

It goes without saying that your examples will not be inlined
allthough evaluating
send([101, 118, 97, 108].pack("C*").gets.chomp
could be done it surely is not worth it.

In other words the attempt to inline a define_method block into a def
would follow an early failure strategy.

I give you an example:

trait {
   behavior :a do 42 end
}
when define_method is called with the block above a very simple and
defensive and fast check would still bring 50% performance gain for
the invocations of a.

Maybe the check can even be done against the bytecode, but I do not
know when the block above is compiled.

Now this is not a feature request but a request to learn more about
the feasibility.

Robert



--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
Posted by Nathan Weizenbaum (nex3)
on 23.04.2008 02:20
(Received via mailing list)
It really isn't possible to statically determine whether eval is called
in a given block, because it's possible that any given function may be
aliased to eval - even after the function is compiled. For example,
consider the following harmless code snippet:

  class Foo
    foo = 12
    define_method(:bar) { puts "foo" }
  end

Perfectly harmless, right? Well, not if I do this:

  class Foo
    alias_method :puts, :eval
  end

Now Foo.new.bar returns 12. In fact, any method could become a call to 
eval.

- Nathan
Posted by Jim Weirich (Guest)
on 23.04.2008 04:39
(Received via mailing list)
On Apr 22, 2008, at 6:48 PM, David A. Black wrote:
> I don't know... it seems to me that determining statically whether or
> not a block will refer back to its context would be really, really
> difficult, at the very least.

Detecting open variables (i.e. variables not bound in the body of
lambda) is fairly easy assuming you have access to the parse tree
(which, of course, any Ruby impl would have).  There are special
cases, as Robert points out "eval" would have to be handled
explicitly.  The question is if there are other special cases beyond
eval that need special handling.

IIRC, Charles Nutter was advocating not allowing "eval" to be aliased
for this exact reason.
Posted by Robert Dober (Guest)
on 23.04.2008 06:19
(Received via mailing list)
On Wed, Apr 23, 2008 at 4:38 AM, Jim Weirich <jim.weirich@gmail.com> 
wrote:
> any Ruby impl would have).  There are special cases, as Robert points out
> "eval" would have to be handled explicitly.  The question is if there are
> other special cases beyond eval that need special handling.
>
>  IIRC, Charles Nutter was advocating not allowing "eval" to be aliased for
> this exact reason.
Yes this was an excellent point made by Nathan and a very
understandably decision by Charles!
Now one still could just keep track of aliases to eval or just set one
flag that disables the inline feature as soon as
any form of eval or send is aliased.
My aggressive fail fast algorithm should of course be shortened not to
even try to evaluate literal arguments of these methods and that would
make it pretty trivial as you have pointed out.
One could even delay inlining the proc in the Hotspot manner say after
the concerned method was called TRESHOLD times.
Well I still would be very happy to stand corrected for every other
oversight....

But assuming that the above can be done, would it be worthy? I mean
how many method invocation would one need that the analysis and
inlining effort would be payed back?

Cheers
Robert


>
>  --
>  -- Jim Weirich
>  -- jim.weirich@gmail.com
>
>
>
>
>
>



--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
Posted by Charles Oliver Nutter (Guest)
on 23.04.2008 08:08
(Received via mailing list)
Jim Weirich wrote:
> question is if there are other special cases beyond eval that need 
> special handling.
> 
> IIRC, Charles Nutter was advocating not allowing "eval" to be aliased 
> for this exact reason.

And JRuby "cheats" currently, deoptimizing in the presence of any "eval"
or similar method call, whether they've been aliased or not. Future
versions will use runtime profiling to determine whether you're
"actually" calling eval or not. But it's a decent trick at the moment,
allowing us to optimize away heap-based variables in many cases.

- Charlie
Posted by Charles Oliver Nutter (Guest)
on 23.04.2008 08:20
(Received via mailing list)
David A. Black wrote:
>>>   define_method(:a) { x }
>> cannot be detected during compilation!
> 
>   define_method(:c) { puts send([101, 118, 97, 108].pack("C*"),
>   gets.chomp) }
> 
> :-)
> 
> I don't know... it seems to me that determining statically whether or
> not a block will refer back to its context would be really, really
> difficult, at the very least.

And thereforce certain types of blocks should not be allowed access to
the enclosing scope/frame. I think the above examples are pretty bad
karma too, and it's hard to argue how their utility outweighs the gross
performance cost they incur.

Check out normal method dispatch versus other types in JRuby. The
difference here is largely whether we can perform certain optimizations
that depend on knowing whether certain variables escape the scope:

                                              user     system      total
        real
control: 100x x100 local var access       0.009000   0.000000   0.009000
(  0.009000)
core class: 100x x100 Fixnum#to_i         0.322000   0.000000   0.322000
(  0.322000)
ruby method: 100k x100 self.foo           0.888000   0.000000   0.888000
(  0.888000)
ruby method: 100k x100 self.foos          1.474000   0.000000   1.474000
(  1.474000)
ruby method: 100k x100 self.foos(1)       1.610000   0.000000   1.610000
(  1.610000)
ruby method: 100k x100 self.foos(4)       5.874000   0.000000   5.874000
(  5.874000)
ruby method: 100k x100 self.foo1          1.137000   0.000000   1.137000
(  1.137000)
ruby method: 100k x100 self.foo2          0.972000   0.000000   0.972000
(  0.972000)
ruby method: 100k x100 self.foo3          1.070000   0.000000   1.070000
(  1.070000)
ruby method: 100k x100 self.foo4          5.355000   0.000000   5.355000
(  5.355000)
__send__ method: 100k x100 self.foo       2.360000   0.000000   2.360000
(  2.360000)
ruby method(opt): 100k x100 self.baz      2.042000   0.000000   2.042000
(  2.042000)
ruby method(no opt): 100k x100 self.baz   1.166000   0.000000   1.166000
(  1.166000)
ruby method(block): 100k x100 self.quux   0.853000   0.000000   0.853000
(  0.853000)
ruby method(block{}): 10k x100 self.quux  0.497000   0.000000   0.497000
(  0.497000)
define_method method: 100k x100 calls     2.538000   0.000000   2.538000
(  2.538000)


And then the same run with some experimental "more optimistic"
frame/scope optimizations:

                                               user     system
total        real
control: 100x x100 local var access       0.006000   0.000000   0.006000
(  0.007000)
core class: 100x x100 Fixnum#to_i         0.253000   0.000000   0.253000
(  0.253000)
ruby method: 100k x100 self.foo           0.413000   0.000000   0.413000
(  0.413000)
ruby method: 100k x100 self.foos          1.104000   0.000000   1.104000
(  1.105000)
ruby method: 100k x100 self.foos(1)       0.884000   0.000000   0.884000
(  0.884000)
ruby method: 100k x100 self.foos(4)       1.067000   0.000000   1.067000
(  1.067000)
ruby method: 100k x100 self.foo1          0.435000   0.000000   0.435000
(  0.434000)
ruby method: 100k x100 self.foo2          0.286000   0.000000   0.286000
(  0.286000)
ruby method: 100k x100 self.foo3          0.352000   0.000000   0.352000
(  0.352000)
ruby method: 100k x100 self.foo4          0.620000   0.000000   0.620000
(  0.620000)
__send__ method: 100k x100 self.foo       1.687000   0.000000   1.687000
(  1.687000)
ruby method(opt): 100k x100 self.baz      1.571000   0.000000   1.571000
(  1.571000)
ruby method(no opt): 100k x100 self.baz   0.448000   0.000000   0.448000
(  0.448000)
ruby method(block): 100k x100 self.quux   0.443000   0.000000   0.443000
(  0.443000)
ruby method(block{}): 10k x100 self.quux  0.434000   0.000000   0.434000
(  0.434000)
define_method method: 100k x100 calls     2.472000   0.000000   2.472000
(  2.472000)


Finally Ruby 1.9 for comparison:

                                               user     system
total        real
control: 100x x100 local var access       0.010000   0.000000   0.010000
(  0.006355)
core class: 100x x100 Fixnum#to_i         0.880000   0.000000   0.880000
(  0.892870)
ruby method: 100k x100 self.foo           0.890000   0.000000   0.890000
(  0.895106)
ruby method: 100k x100 self.foos          5.310000   0.010000   5.320000
(  5.307588)
ruby method: 100k x100 self.foos(1)       5.490000   0.010000   5.500000
(  5.506018)
ruby method: 100k x100 self.foos(4)       5.710000   0.010000   5.720000
(  5.709975)
ruby method: 100k x100 self.foo1          0.950000   0.000000   0.950000
(  0.949075)
ruby method: 100k x100 self.foo2          1.000000   0.000000   1.000000
(  0.997541)
ruby method: 100k x100 self.foo3          1.040000   0.000000   1.040000
(  1.048048)
ruby method: 100k x100 self.foo4          1.130000   0.000000   1.130000
(  1.129857)
__send__ method: 100k x100 self.foo       1.060000   0.010000   1.070000
(  1.063126)
ruby method(opt): 100k x100 self.baz      2.430000   0.000000   2.430000
(  2.433559)
ruby method(no opt): 100k x100 self.baz   1.050000   0.000000   1.050000
(  1.046770)
ruby method(block): 100k x100 self.quux   0.970000   0.000000   0.970000
(  0.974937)
ruby method(block{}): 10k x100 self.quux  0.610000   0.000000   0.610000
(  0.606299)
define_method method: 100k x100 calls     2.170000   0.010000   2.180000
(  2.172675)


The ability to make optimizations depending on the callee's access to
your frame/scope makes a tremendous difference in performance. With the
more optimistic optimizations, JRuby's method dispatch can be as much as
  4-8x as fast as Ruby 1.9.

Benchmark source is here:
http://svn.codehaus.org/jruby/trunk/jruby/test/bench/language/bench_method_dispatch.rb

- Charlie
Posted by Charles Oliver Nutter (Guest)
on 23.04.2008 08:24
(Received via mailing list)
David A. Black wrote:
> Hi --
> 
> On Wed, 23 Apr 2008, Robert Dober wrote:
>> David I am afraid I do not get the point,
>> you just gave another example of a method that cannot be "inlined"
>> because of the
>> eval, but that is easily detectable during compile time.
>> I would need an example where the closure is needed and this fact
>> cannot be detected during compilation!
...
> I don't know... it seems to me that determining statically whether or
> not a block will refer back to its context would be really, really
> difficult, at the very least.

Also...regardless of whether such determination can be made statically,
it can certainly be made dynamically. I have prototype code for JRuby
that does exactly this during the interpretation phase, using that
information during JIT to better optimize the method. So there's
certainly a way to do it, but more consistent and helpful static hints
would make it a lot easier and at least possible on static-compiling
implementations like Ruby 1.9, IronRuby, and Rubinius.

- Charlie
Posted by Joel VanderWerf (Guest)
on 23.04.2008 09:01
(Received via mailing list)
Charles Oliver Nutter wrote:
> ...
> implementations like Ruby 1.9, IronRuby, and Rubinius.
Will jruby de-optimize dynamically, as in this case?

class C
   x = 100
   define_method(:b){puts foo("x")}
   def foo arg; "FOO #{arg}"; end
end

c = C.new
c.b
class << c; alias foo eval; end
c.b
Posted by ts (Guest)
on 23.04.2008 10:38
(Received via mailing list)
Robert Dober wrote:
> However, if and only if, the block in the define_method does not use
> the closure we could really inline the block as the code of a method
> defined with def.

 What do you want to say with "inline" ?

vgs% ./ruby -e 'puts VM::InstructionSequence.compile("proc {|x| x = 12 
}.call(1)").disasm'
== disasm: <ISeq:<compiled>@<compiled>>=================================
== catch table
| catch type: break  st: 0000 ed: 0007 sp: 0000 cont: 0007
|------------------------------------------------------------------------
0000 putnil                                                           ( 
1)
0001 send             :proc, 0, block in <compiled>, 8, <ic>
0007 putobject        1
0009 send             :call, 1, nil, 0, <ic>
0015 leave
== disasm: <ISeq:block in <compiled>@<compiled>>========================
== catch table
| catch type: redo   st: 0000 ed: 0006 sp: 0000 cont: 0000
| catch type: next   st: 0000 ed: 0006 sp: 0000 cont: 0006
|------------------------------------------------------------------------
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1] 
s3)
[ 1] x<Arg>
0000 putobject        12                                              ( 
1)
0002 dup
0003 setdynamic       x, 0
0006 leave
vgs%


vgs% ./ruby -e 'puts VM::InstructionSequence.compile("def a(x) x = 12; 
end; a(1)").disasm'
== disasm: <ISeq:<compiled>@<compiled>>=================================
0000 putnil                                                           ( 
1)
0001 definemethod     :a, a, 0
0005 putnil
0006 putobject        1
0008 send             :a, 1, nil, 8, <ic>
0014 leave
== disasm: <ISeq:a@<compiled>>==========================================
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1] 
s1)
[ 2] x<Arg>
0000 putobject        12                                              ( 
1)
0002 dup
0003 setlocal         x
0005 leave
vgs%



Guy Decoux
Posted by Robert Dober (Guest)
on 23.04.2008 11:15
(Received via mailing list)
On Wed, Apr 23, 2008 at 10:37 AM, ts <decoux@moulon.inra.fr> wrote:
>  | catch type: break  st: 0000 ed: 0007 sp: 0000 cont: 0007
>  |------------------------------------------------------------------------
>  == disasm: <ISeq:<compiled>@<compiled>>=================================
>  0002 dup
>  0003 setlocal         x
>  0005 leave
>  vgs%
>
>
>
>  Guy Decoux
>
>

basically I mean that IS.compile("def a; 42 end") ==
IS.compile("define_method :a do 42 end").

Read == as "could be" please.

R.




--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
Posted by ts (Guest)
on 23.04.2008 11:27
(Received via mailing list)
Robert Dober wrote:
> basically I mean that IS.compile("def a; 42 end") ==
> IS.compile("define_method :a do 42 end").

 Well you can do it like me

vgs% ./ruby -e 'puts VM::InstructionSequence.compile("def a() 12; 
end").disasm'
== disasm: <ISeq:<compiled>@<compiled>>=================================
0000 putnil                                                           ( 
1)
0001 definemethod     :a, a, 0
0005 putnil
0006 leave
== disasm: <ISeq:a@<compiled>>==========================================
0000 putobject        12                                              ( 
1)
0002 leave
vgs%

vgs% ./ruby -e 'puts VM::InstructionSequence.compile("define_method :a 
do 12; end").disasm'
== disasm: <ISeq:<compiled>@<compiled>>=================================
== catch table
| catch type: break  st: 0000 ed: 0009 sp: 0000 cont: 0009
|------------------------------------------------------------------------
0000 putnil                                                           ( 
1)
0001 putobject        :a
0003 send             :define_method, 1, block in <compiled>, 8, <ic>
0009 leave
== disasm: <ISeq:block in <compiled>@<compiled>>========================
== catch table
| catch type: redo   st: 0000 ed: 0002 sp: 0000 cont: 0000
| catch type: next   st: 0000 ed: 0002 sp: 0000 cont: 0002
|------------------------------------------------------------------------
0000 putobject        12                                              ( 
1)
0002 leave
vgs%


 You really think that it exist a big difference *at compile
 time* ?

Guy Decoux
Posted by Charles Oliver Nutter (Guest)
on 23.04.2008 15:25
(Received via mailing list)
Joel VanderWerf wrote:
> Will jruby de-optimize dynamically, as in this case?

Not at the moment, and it's an entertaining way to break JRuby for fun
and profit. I could add in the check for eval being aliased, and display
a warning and deoptimize everything, but I currently do not because I've
been waiting to see if any libraries break. So far, nobody does this.

The dynamic optimizations that will eventually go into place would check
for this, so that if the methods seen previously suddenly change
(aliased, redefined) the original code will be backed down.

- Charlie
Posted by Paul Brannan (cout)
on 23.04.2008 15:51
(Received via mailing list)
On Wed, Apr 23, 2008 at 10:24:43PM +0900, Charles Oliver Nutter wrote:
> Not at the moment, and it's an entertaining way to break JRuby for fun 
> and profit. I could add in the check for eval being aliased, and display 
> a warning and deoptimize everything, but I currently do not because I've 
> been waiting to see if any libraries break. So far, nobody does this.

Maybe it should be vocal, even if you don't deoptimize:

  test.rb:42: warning: aliasing eval?  you evil, evil person.

If it's not vocal, one might not notice right away that something broke.

Paul
Posted by Paul Brannan (cout)
on 23.04.2008 16:09
(Received via mailing list)
On Wed, Apr 23, 2008 at 04:03:09AM +0900, Robert Dober wrote:
> module M
>   x = 42
>   define_method :a do x end # Cannot be inlined
>   define_method :b do some_call; @x end # Can be inlined as if def were used
> end

You can "inline" (it's really not the right word) either one using
nodewrap, but it's very evil to do:

/home/user> irb1.9
irb(main):001:0> require 'nodewrap'
=> true
irb(main):002:0> p = proc { 42 }
=> #<Proc:0xb7b848d8@(irb):2>
irb(main):003:0> p.body
=> <ISeq:block (3 levels) in irb_binding@(irb)>
irb(main):004:0> class Foo; end
=> nil
irb(main):005:0> Foo.add_method :foo, p.body, Noex::PUBLIC
=> nil
irb(main):006:0> Foo.new.foo
=> 42

Paul
Posted by Charles Oliver Nutter (Guest)
on 23.04.2008 16:38
(Received via mailing list)
Paul Brannan wrote:
> If it's not vocal, one might not notice right away that something broke.
Probably a good idea. I'll file a bug.

- Charlie
Posted by Robert Dober (Guest)
on 23.04.2008 18:02
(Received via mailing list)
On Wed, Apr 23, 2008 at 11:25 AM, ts <decoux@moulon.inra.fr> wrote:

>
>
>   You really think that it exist a big difference *at compile
>   time* ?

No Guy, at *runtime* about 50% slower. Please have a look at Charles'
benchmarks I would never dare posting mine now ;).
Salut
Robert

--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
Posted by Robert Dober (Guest)
on 23.04.2008 18:12
(Received via mailing list)
On Wed, Apr 23, 2008 at 10:37 AM, ts <decoux@moulon.inra.fr> wrote:
> Robert Dober wrote:
>  > However, if and only if, the block in the define_method does not use
>  > the closure we could really inline the block as the code of a method
>  > defined with def.
>
>   What do you want to say with "inline" ?
As others pointed out "inline" surely is not the best name, but I was
completely ignorant when starting this thread.
I learnt so much already here I am not stopping now ;)
Charles how would you denominate this optimization if I may dare
asking YAFM [ yet another five minutes (c) Robert Dober]
of your time.
Thx a lot in advance
R.
Posted by ts (Guest)
on 23.04.2008 18:19
(Received via mailing list)
Robert Dober wrote:
> No Guy, at *runtime* about 50% slower. Please have a look at Charles'
> benchmarks I would never dare posting mine now ;).

 and you're not surprised by the difference between self.foo and
 self.foos ?


Guy Decoux
Posted by Robert Dober (Guest)
on 23.04.2008 19:56
(Received via mailing list)
On Wed, Apr 23, 2008 at 6:19 PM, ts <decoux@moulon.inra.fr> wrote:
> Robert Dober wrote:
>  > No Guy, at *runtime* about 50% slower. Please have a look at Charles'
>  > benchmarks I would never dare posting mine now ;).
>
>   and you're not surprised by the difference between self.foo and
>   self.foos ?

I had not looked into that because it was not my point of interest,
but now that you mention it, it is indeed amazing how costly the splat
seems to be.
Another thing I find slightly surprising is that Charles uses
Benchmark#bm instead of Benchmark#bmbm, probably a JRuby internal
reason but just wondering...

Cheers
R.
--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein
Posted by Charles Oliver Nutter (Guest)
on 23.04.2008 19:58
(Received via mailing list)
Robert Dober wrote:
> Charles how would you denominate this optimization if I may dare
> asking YAFM [ yet another five minutes (c) Robert Dober]
> of your time.

Hmm, block unwrapping? Essentially what you want to do to make
define_method methods as fast as regular methods is to make them regular
methods; that means unwrapping the block and making its body the body of
an actual method, rather than doing block dispatch semantics wrapped in
method dispatch semantics. And with 1.9's much more restrictive block
arguments, you're not losing much there. Obviously you lose the fact
that it might be a true closure, and you'd have to go through the
AST/bytecode and check for variable depths > 0, but it would be worth it
to make performance decent. IMHO define_method would be a lot more
useful if it actually made a (fast) method rather than an ugly (slow)
conglomeration of a method and a proc.

- Charlie
Posted by Jim Weirich (Guest)
on 23.04.2008 20:08
(Received via mailing list)
On Apr 23, 2008, at 1:56 PM, Charles Oliver Nutter wrote:
> IMHO define_method would be a lot more useful if it actually made a  
> (fast) method rather than an ugly (slow) conglomeration of a method  
> and a proc.

Amen.
Posted by Paul Brannan (cout)
on 23.04.2008 22:17
(Received via mailing list)
On Thu, Apr 24, 2008 at 02:56:54AM +0900, Charles Oliver Nutter wrote:
> conglomeration of a method and a proc.
But if you don't need to access variables outside your scope, then why
use define_method at all?  In that case def works just fine.

Paul
Posted by Charles Oliver Nutter (Guest)
on 23.04.2008 22:37
(Received via mailing list)
Paul Brannan wrote:
>> useful if it actually made a (fast) method rather than an ugly (slow) 
>> conglomeration of a method and a proc.
> 
> But if you don't need to access variables outside your scope, then why
> use define_method at all?  In that case def works just fine.

define_method :foo, &some_proc

- Charlie
Posted by Robert Dober (Guest)
on 24.04.2008 06:01
(Received via mailing list)
On Wed, Apr 23, 2008 at 8:06 PM, Jim Weirich <jim.weirich@gmail.com> 
wrote:
> On Apr 23, 2008, at 1:56 PM, Charles Oliver Nutter wrote:
>
> > IMHO define_method would be a lot more useful if it actually made a (fast)
> method rather than an ugly (slow) conglomeration of a method and a proc.
> >
>
>  Amen.
Yup that's why I asked :)
Robert
--
http://ruby-smalltalk.blogspot.com/

---
Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein