Fast Method Source

Description

Fast Method Source is a Ruby library for quering methods, procs and lambdas for their source code and comments.

require 'fast_method_source'
require 'fast_method_source/core_ext'
require 'set'

puts Set.instance_method(:merge).source

Output.

  def merge(enum)
    if enum.instance_of?(self.class)
      @hash.update(enum.instance_variable_get(:@hash))
    else
      do_with_enum(enum) { |o| add(o) }
    end

    self
  end

Installation

All you need is to install the gem.

gem install fast_method_source

Synopsis

Fast Method Source provides similar functionality to method_source, which is being used by the Pry REPL, but with a number of major key differences.

Speed improvements

The main goal of Fast Method Source is to be as fast as possible. In result, the library is much faster than method_source. What's acceptable for Pry, when you query only one method, is not acceptable for other use cases such as querying all the methods defined in your Ruby process. The benchmark showed that Fast Method Source is about 5-10x faster than its competitor.

I'd be remiss if I didn't mention that there's also room for further speed improvements. The benchmarks below represent comparison of both libraries.

#comment_and_source

ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]

This is a utility method and method_source doesn't feature it.

Processor: Intel(R) Core(TM) i5-2410M CPU @ 2.30GHz
Platform: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Counting the number of sample methods...
Sample methods: 19438
       user     system      total        real
FastMethodSource#comment_and_source_for 16.240000   1.180000  17.420000 ( 19.439981)

#source

ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Processor: Intel(R) Core(TM) i5-2410M CPU @ 2.30GHz
Platform: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Counting the number of sample methods...
Sample methods: 19438
Rehearsal -----------------------------------------------------------
FastMethodSource#source  14.220000   0.880000  15.100000 ( 16.844189)
MethodSource#source     104.140000   0.420000 104.560000 (126.567209)
------------------------------------------------ total: 119.660000sec

                              user     system      total        real
FastMethodSource#source  14.920000   0.890000  15.810000 ( 17.658905)
MethodSource#source      96.860000   0.410000  97.270000 (108.131119)

#comment

ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Platform: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Counting the number of sample methods...
Sample methods: 19596
Rehearsal ------------------------------------------------------------
FastMethodSource#comment   1.790000   0.210000   2.000000 (  2.229802)
MethodSource#comment      85.020000   0.370000  85.390000 (103.061652)
-------------------------------------------------- total: 87.390000sec

                               user     system      total        real
FastMethodSource#comment   1.620000   0.250000   1.870000 (  2.072023)
MethodSource#comment      84.560000   0.320000  84.880000 ( 94.465574)

Correctness of output

Fast Method Source is capable of displaying source code even for dynamically defined methods (there are some crazy methods in stdlib).

require 'fast_method_source'
require 'rss'

puts RSS::Maker::ChannelBase.instance_method(:dc_description=).source

Output.

            def #{name}=(#{new_value_variable_name})
              #{local_variable_name} =
                #{plural_name}.first || #{plural_name}.new_#{new_name}
              #{additional_setup_code}
              #{local_variable_name}.#{attribute} = #{new_value_variable_name}
            end

RAM consumption

The comment_and_source benchmark shows that at this moment the library uses about 450 MB of RAM.

API

General description

The library provides the following methods: #comment, #source and #comment_and_source. There are two ways to use Fast Method Source. One way is to monkey-patch relevant core Ruby classes and use the methods directly.

require 'fast_method_source'
require 'fast_method_source/core_ext'

# With UnboundMethod
Set.instance_method(:merge).source
#=> "  def merge(enum)\n..."

# With Method
Set.method(:[]).source
#=> "  def self.[](*ary)\n..."

# With Proc (or lambda)
myproc = proc { |arg|
  arg + 1
}
myproc.source
#=> "myproc = proc { |arg|\n..."

The other way is by using these methods defined on the library's class directly.

require 'fast_method_source'

# With UnboundMethod
FastMethodSource.source_for(Set.instance_method(:merge))
#=> "  def merge(enum)\n..."

# With Method
FastMethodSource.source_for(Set.method(:[]))
#=> "  def self.[](*ary)\n..."

# With Proc (or lambda)
myproc = proc { |arg|
  arg + 1
}
FastMethodSource.source_for(myproc)
#=> "myproc = proc { |arg|\n..."

Methods

FastMethodSource#source_for(method)

Returns the source code of the given method as a String. Raises FastMethodSource::SourceNotFoundError if:

  • method is defined outside of a file (for example, in a REPL)
  • method doesn't have a source location (typically C methods)

Raises IOError if:

  • the file location of method is inaccessible
FastMethodSource.source_for(Set.instance_method(:merge))
#=> "  def merge(enum)\n..."
FastMethodSource.source_for(Array.instance_method(:pop))
#=> FastMethodSource::SourceNotFoundError

FastMethodSource#comment_for(method)

Returns the comment of the given method as a String. The rest is identical to FastMethodSource#source_for(method).

FastMethodSource#comment_and_source_for(method)

Returns the comment and the source code of the given method as a String (the order is the same as the method's name). The rest is identical to FastMethodSource#source_for(method).

Limitations

Rubies

  • CRuby 2.2.2
  • CRuby 2.3.0dev and higher

Rubies below 2.2.2 were not tested, so in theory if it quacks like Ruby 2, it may work.

OS'es

  • GNU/Linux
  • Mac OS (hopefully)

Roadmap

Further speed improvements

Although Fast Method Source is faster than any of its competitors, it's still very slow. On average, a mature Rails 4 application has at least 45K methods. In order to query all those methods for their source code, you would need to wait for a while and perhaps to drink a cup of tea.

The goal of the project is to be able to query 50K methods in less than 15 seconds. Whether it's possible or not is to be determined.

Decrease memory consumption

I'm not happy about the current rates. To be investigated.

Licence

The project uses Zlib License. See the LICENCE.txt file for more information.