ruby / ruby/rbs

`RBS::Prototype::Runtime#block_from_ast_of` is dominant in terms of execution time.

Open
#1,565 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Ruby
Stars
2.2k
Forks
256
Avg merge
6d 17h
Merged PRs (30d)
37

Description

Most of the execution time in rbs prototype runtime is taken up by RBS::Prototype::Runtime#block_from_ast_of.
This means that a significant amount of cost is being spent just to investigate whether a method's block is mandatory or not.
For example, when running rbs prototype runtime targeting ActiveSupport::*, 65% of the time is spent on this process.

The behavior of RBS::Prototype::Runtime#block_from_ast_of is not perfect, as I pointed out in https://github.com/ruby/rbs/pull/1180#issuecomment-1360942094, and I believe a perfect judgment is fundamentally very difficult.

I feel that the use of rbs prototype runtime is being avoided due to its slow execution time.

Almost all of the execution time for RBS::Prototype::Runtime#block_from_ast_of comes from RubyVM::AbstractSyntaxTree.of, and there's no room for optimization.

I propose that rbs prototype runtime block arguments should always be considered optional.

Benchmark script
require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'activesupport'
  gem 'stackprof'
end

require 'active_support/all'

ActiveSupport.eager_load!

$LOAD_PATH.unshift << File.expand_path("../lib", __dir__)
require 'rbs'

env = RBS::Environment.from_loader(RBS::EnvironmentLoader.new).resolve_type_names

StackProf.run(mode: :wall, out: "stackprof-wall.dump", interval: 100) do

RBS::Prototype::Runtime.new(
  patterns: ["ActiveSupport::*"],
  env: env,
  merge: false,
  todo: false,
  owners_included: []
).decls

end
$ bundle exec stackprof stackprof-wall.dump
==================================
  Mode: wall(100)
  Samples: 2959 (16.22% miss rate)
  GC: 519 (17.54%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      1927  (65.1%)        1925  (65.1%)     RubyVM::AbstractSyntaxTree.of
       339  (11.5%)         339  (11.5%)     (sweeping)
       266   (9.0%)         266   (9.0%)     (marking)
        33   (1.1%)          29   (1.0%)     Kernel#require
      2261  (76.4%)          24   (0.8%)     RBS::Prototype::Runtime#generate_methods
        41   (1.4%)          24   (0.8%)     Class#new
        69   (2.3%)          18   (0.6%)     RBS::Prototype::Helpers#each_node
        17   (0.6%)          17   (0.6%)     RubyVM::AbstractSyntaxTree::Node#type
        15   (0.5%)          15   (0.5%)     Module#public_instance_methods
        71   (2.4%)          15   (0.5%)     RBS::Prototype::Helpers#any_node?
        48   (1.6%)          15   (0.5%)     Module#const_get
        14   (0.5%)          14   (0.5%)     Module#private_instance_methods
        14   (0.5%)          14   (0.5%)     String#split
        14   (0.5%)          14   (0.5%)     Module#instance_method
        14   (0.5%)          14   (0.5%)     String#inspect
        13   (0.4%)          13   (0.4%)     RubyVM::AbstractSyntaxTree::Node#children
      2126  (71.8%)          12   (0.4%)     RBS::Prototype::Runtime#method_type
        32   (1.1%)          10   (0.3%)     RBS::Prototype::Runtime#each_mixined_module_one
        23   (0.8%)           9   (0.3%)     Array#inspect
        13   (0.4%)           9   (0.3%)     RBS::Prototype::Runtime#ensure_outer_module_declarations
        24   (0.8%)           8   (0.3%)     RBS::Prototype::Runtime#target_method?
        37   (1.3%)           8   (0.3%)     Array#select
        79   (2.7%)           7   (0.2%)     RBS::Prototype::Helpers#block_from_body
        28   (0.9%)           7   (0.2%)     RBS::Prototype::Runtime#const_name
         5   (0.2%)           5   (0.2%)     UnboundMethod#parameters
         5   (0.2%)           5   (0.2%)     Symbol#match?
         5   (0.2%)           4   (0.1%)     Array#sort
      2399  (81.1%)           4   (0.1%)     Array#each
        69   (2.3%)           4   (0.1%)     RBS::Prototype::Helpers#each_child
         3   (0.1%)           3   (0.1%)     Kernel#singleton_class
$ bundle exec stackprof --method RubyVM::AbstractSyntaxTree.of stackprof-wall.dump
RubyVM::AbstractSyntaxTree.of (<internal:ast>:96)
  samples:  1925 self (65.1%)  /   1927 total (65.1%)
  callers:
    1927  (  100.0%)  RBS::Prototype::Runtime#block_from_ast_of
  callees (2 total):
       2  (  100.0%)  Exception#initialize
  code:
        SOURCE UNAVAILABLE

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with RBS::Prototype::Runtime#block_from_ast_of and its caller in #generate_methods, then run the benchmark script from the issue against ActiveSupport::*. Change the runtime prototype behavior so block arguments are treated as optional, and confirm the benchmark no longer spends most of its time in RubyVM::AbstractSyntaxTree.of.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
performance, tooling
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.