bootsnap 1.4.0 → 1.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -0
  3. data/README.md +68 -13
  4. data/exe/bootsnap +5 -0
  5. data/ext/bootsnap/bootsnap.c +285 -87
  6. data/ext/bootsnap/extconf.rb +20 -14
  7. data/lib/bootsnap/bundler.rb +1 -0
  8. data/lib/bootsnap/cli/worker_pool.rb +135 -0
  9. data/lib/bootsnap/cli.rb +246 -0
  10. data/lib/bootsnap/compile_cache/iseq.rb +24 -7
  11. data/lib/bootsnap/compile_cache/yaml.rb +113 -39
  12. data/lib/bootsnap/compile_cache.rb +15 -2
  13. data/lib/bootsnap/explicit_require.rb +1 -0
  14. data/lib/bootsnap/load_path_cache/cache.rb +44 -9
  15. data/lib/bootsnap/load_path_cache/change_observer.rb +5 -1
  16. data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +30 -6
  17. data/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb +11 -0
  18. data/lib/bootsnap/load_path_cache/loaded_features_index.rb +43 -11
  19. data/lib/bootsnap/load_path_cache/path.rb +3 -2
  20. data/lib/bootsnap/load_path_cache/path_scanner.rb +53 -27
  21. data/lib/bootsnap/load_path_cache/realpath_cache.rb +5 -5
  22. data/lib/bootsnap/load_path_cache/store.rb +28 -14
  23. data/lib/bootsnap/load_path_cache.rb +10 -16
  24. data/lib/bootsnap/setup.rb +2 -33
  25. data/lib/bootsnap/version.rb +2 -1
  26. data/lib/bootsnap.rb +91 -15
  27. metadata +17 -29
  28. data/.gitignore +0 -17
  29. data/.rubocop.yml +0 -20
  30. data/.travis.yml +0 -24
  31. data/CODE_OF_CONDUCT.md +0 -74
  32. data/CONTRIBUTING.md +0 -21
  33. data/Gemfile +0 -8
  34. data/README.jp.md +0 -231
  35. data/Rakefile +0 -12
  36. data/bin/ci +0 -10
  37. data/bin/console +0 -14
  38. data/bin/setup +0 -8
  39. data/bin/test-minimal-support +0 -7
  40. data/bin/testunit +0 -8
  41. data/bootsnap.gemspec +0 -45
  42. data/dev.yml +0 -10
  43. data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +0 -100
  44. data/shipit.rubygems.yml +0 -4
data/lib/bootsnap.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative('bootsnap/version')
2
4
  require_relative('bootsnap/bundler')
3
5
  require_relative('bootsnap/load_path_cache')
@@ -6,42 +8,116 @@ require_relative('bootsnap/compile_cache')
6
8
  module Bootsnap
7
9
  InvalidConfiguration = Class.new(StandardError)
8
10
 
11
+ class << self
12
+ attr_reader :logger
13
+ end
14
+
15
+ def self.log!
16
+ self.logger = $stderr.method(:puts)
17
+ end
18
+
19
+ def self.logger=(logger)
20
+ @logger = logger
21
+ if logger.respond_to?(:debug)
22
+ self.instrumentation = ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") }
23
+ else
24
+ self.instrumentation = ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") }
25
+ end
26
+ end
27
+
28
+ def self.instrumentation=(callback)
29
+ @instrumentation = callback
30
+ if respond_to?(:instrumentation_enabled=, true)
31
+ self.instrumentation_enabled = !!callback
32
+ end
33
+ end
34
+
35
+ def self._instrument(event, path)
36
+ @instrumentation.call(event, path)
37
+ end
38
+
9
39
  def self.setup(
10
40
  cache_dir:,
11
41
  development_mode: true,
12
42
  load_path_cache: true,
13
- autoload_paths_cache: true,
14
- disable_trace: false,
43
+ autoload_paths_cache: nil,
44
+ disable_trace: nil,
15
45
  compile_cache_iseq: true,
16
46
  compile_cache_yaml: true
17
47
  )
18
- if autoload_paths_cache && !load_path_cache
19
- raise(InvalidConfiguration, "feature 'autoload_paths_cache' depends on feature 'load_path_cache'")
48
+ unless autoload_paths_cache.nil?
49
+ warn "[DEPRECATED] Bootsnap's `autoload_paths_cache:` option is deprecated and will be removed. " \
50
+ "If you use Zeitwerk this option is useless, and if you are still using the classic autoloader " \
51
+ "upgrading is recommended."
20
52
  end
21
53
 
22
- setup_disable_trace if disable_trace
54
+ unless disable_trace.nil?
55
+ warn "[DEPRECATED] Bootsnap's `disable_trace:` option is deprecated and will be removed. " \
56
+ "If you use Ruby 2.5 or newer this option is useless, if not upgrading is recommended."
57
+ end
58
+
59
+ if compile_cache_iseq && !iseq_cache_supported?
60
+ warn "Ruby 2.5 has a bug that break code tracing when code is loaded from cache. It is recommened " \
61
+ "to turn `compile_cache_iseq` off on Ruby 2.5"
62
+ end
23
63
 
24
64
  Bootsnap::LoadPathCache.setup(
25
- cache_path: cache_dir + '/bootsnap-load-path-cache',
65
+ cache_path: cache_dir + '/bootsnap/load-path-cache',
26
66
  development_mode: development_mode,
27
- active_support: autoload_paths_cache
28
67
  ) if load_path_cache
29
68
 
30
69
  Bootsnap::CompileCache.setup(
31
- cache_dir: cache_dir + '/bootsnap-compile-cache',
70
+ cache_dir: cache_dir + '/bootsnap/compile-cache',
32
71
  iseq: compile_cache_iseq,
33
72
  yaml: compile_cache_yaml
34
73
  )
35
74
  end
36
75
 
37
- def self.setup_disable_trace
38
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
39
- warn(
40
- "from #{caller_locations(1, 1)[0]}: The 'disable_trace' method is not allowed with this Ruby version. " \
41
- "current: #{RUBY_VERSION}, allowed version: < 2.5.0",
76
+ def self.iseq_cache_supported?
77
+ return @iseq_cache_supported if defined? @iseq_cache_supported
78
+
79
+ ruby_version = Gem::Version.new(RUBY_VERSION)
80
+ @iseq_cache_supported = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0')
81
+ end
82
+
83
+ def self.default_setup
84
+ env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV']
85
+ development_mode = ['', nil, 'development'].include?(env)
86
+
87
+ unless ENV['DISABLE_BOOTSNAP']
88
+ cache_dir = ENV['BOOTSNAP_CACHE_DIR']
89
+ unless cache_dir
90
+ config_dir_frame = caller.detect do |line|
91
+ line.include?('/config/')
92
+ end
93
+
94
+ unless config_dir_frame
95
+ $stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
96
+ $stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or")
97
+ $stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR")
98
+
99
+ raise("couldn't infer bootsnap cache directory")
100
+ end
101
+
102
+ path = config_dir_frame.split(/:\d+:/).first
103
+ path = File.dirname(path) until File.basename(path) == 'config'
104
+ app_root = File.dirname(path)
105
+
106
+ cache_dir = File.join(app_root, 'tmp', 'cache')
107
+ end
108
+
109
+
110
+ setup(
111
+ cache_dir: cache_dir,
112
+ development_mode: development_mode,
113
+ load_path_cache: !ENV['DISABLE_BOOTSNAP_LOAD_PATH_CACHE'],
114
+ compile_cache_iseq: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'] && iseq_cache_supported?,
115
+ compile_cache_yaml: !ENV['DISABLE_BOOTSNAP_COMPILE_CACHE'],
42
116
  )
43
- else
44
- RubyVM::InstructionSequence.compile_option = { trace_instruction: false }
117
+
118
+ if ENV['BOOTSNAP_LOG']
119
+ log!
120
+ end
45
121
  end
46
122
  end
47
123
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.7.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-11 00:00:00.000000000 Z
11
+ date: 2021-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,28 +28,28 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake-compiler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
@@ -97,34 +97,23 @@ dependencies:
97
97
  description: Boot large ruby/rails apps faster
98
98
  email:
99
99
  - burke.libbey@shopify.com
100
- executables: []
100
+ executables:
101
+ - bootsnap
101
102
  extensions:
102
103
  - ext/bootsnap/extconf.rb
103
104
  extra_rdoc_files: []
104
105
  files:
105
- - ".gitignore"
106
- - ".rubocop.yml"
107
- - ".travis.yml"
108
106
  - CHANGELOG.md
109
- - CODE_OF_CONDUCT.md
110
- - CONTRIBUTING.md
111
- - Gemfile
112
107
  - LICENSE.txt
113
- - README.jp.md
114
108
  - README.md
115
- - Rakefile
116
- - bin/ci
117
- - bin/console
118
- - bin/setup
119
- - bin/test-minimal-support
120
- - bin/testunit
121
- - bootsnap.gemspec
122
- - dev.yml
109
+ - exe/bootsnap
123
110
  - ext/bootsnap/bootsnap.c
124
111
  - ext/bootsnap/bootsnap.h
125
112
  - ext/bootsnap/extconf.rb
126
113
  - lib/bootsnap.rb
127
114
  - lib/bootsnap/bundler.rb
115
+ - lib/bootsnap/cli.rb
116
+ - lib/bootsnap/cli/worker_pool.rb
128
117
  - lib/bootsnap/compile_cache.rb
129
118
  - lib/bootsnap/compile_cache/iseq.rb
130
119
  - lib/bootsnap/compile_cache/yaml.rb
@@ -132,7 +121,6 @@ files:
132
121
  - lib/bootsnap/load_path_cache.rb
133
122
  - lib/bootsnap/load_path_cache/cache.rb
134
123
  - lib/bootsnap/load_path_cache/change_observer.rb
135
- - lib/bootsnap/load_path_cache/core_ext/active_support.rb
136
124
  - lib/bootsnap/load_path_cache/core_ext/kernel_require.rb
137
125
  - lib/bootsnap/load_path_cache/core_ext/loaded_features.rb
138
126
  - lib/bootsnap/load_path_cache/loaded_features_index.rb
@@ -142,7 +130,6 @@ files:
142
130
  - lib/bootsnap/load_path_cache/store.rb
143
131
  - lib/bootsnap/setup.rb
144
132
  - lib/bootsnap/version.rb
145
- - shipit.rubygems.yml
146
133
  homepage: https://github.com/Shopify/bootsnap
147
134
  licenses:
148
135
  - MIT
@@ -150,6 +137,7 @@ metadata:
150
137
  bug_tracker_uri: https://github.com/Shopify/bootsnap/issues
151
138
  changelog_uri: https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md
152
139
  source_code_uri: https://github.com/Shopify/bootsnap
140
+ allowed_push_host: https://rubygems.org
153
141
  post_install_message:
154
142
  rdoc_options: []
155
143
  require_paths:
@@ -158,14 +146,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
158
146
  requirements:
159
147
  - - ">="
160
148
  - !ruby/object:Gem::Version
161
- version: 2.0.0
149
+ version: 2.3.0
162
150
  required_rubygems_version: !ruby/object:Gem::Requirement
163
151
  requirements:
164
152
  - - ">="
165
153
  - !ruby/object:Gem::Version
166
154
  version: '0'
167
155
  requirements: []
168
- rubygems_version: 3.0.2
156
+ rubygems_version: 3.2.20
169
157
  signing_key:
170
158
  specification_version: 4
171
159
  summary: Boot large ruby/rails apps faster
data/.gitignore DELETED
@@ -1,17 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- *.bundle
11
- *.so
12
- *.o
13
- *.a
14
- *.gem
15
- *.db
16
- mkmf.log
17
- .rubocop-*
data/.rubocop.yml DELETED
@@ -1,20 +0,0 @@
1
- inherit_from:
2
- - http://shopify.github.io/ruby-style-guide/rubocop.yml
3
-
4
- AllCops:
5
- Exclude:
6
- - 'vendor/**/*'
7
- - 'tmp/**/*'
8
- TargetRubyVersion: '2.2'
9
-
10
- # This doesn't take into account retrying from an exception
11
- Lint/HandleExceptions:
12
- Enabled: false
13
-
14
- # allow String.new to create mutable strings
15
- Style/EmptyLiteral:
16
- Enabled: false
17
-
18
- # allow the use of globals which makes sense in a CLI app like this
19
- Style/GlobalVars:
20
- Enabled: false
data/.travis.yml DELETED
@@ -1,24 +0,0 @@
1
- language: ruby
2
- sudo: false
3
-
4
- os:
5
- - linux
6
- - osx
7
-
8
- rvm:
9
- - ruby-2.4
10
- - ruby-2.5
11
- - ruby-head
12
-
13
- matrix:
14
- allow_failures:
15
- - rvm: ruby-head
16
- include:
17
- - rvm: jruby
18
- os: linux
19
- env: MINIMAL_SUPPORT=1
20
- - rvm: truffleruby
21
- os: linux
22
- env: MINIMAL_SUPPORT=1
23
-
24
- script: bin/ci
data/CODE_OF_CONDUCT.md DELETED
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at burke@libbey.me. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
data/CONTRIBUTING.md DELETED
@@ -1,21 +0,0 @@
1
- # Contributing to Bootsnap
2
-
3
- We love receiving pull requests!
4
-
5
- ## Standards
6
-
7
- * PR should explain what the feature does, and why the change exists.
8
- * PR should include any carrier specific documentation explaining how it works.
9
- * Code _must_ be tested, including both unit and remote tests where applicable.
10
- * Be consistent. Write clean code that follows [Ruby community standards](https://github.com/bbatsov/ruby-style-guide).
11
- * Code should be generic and reusable.
12
-
13
- If you're stuck, ask questions!
14
-
15
- ## How to contribute
16
-
17
- 1. Fork it ( https://github.com/Shopify/bootsnap/fork )
18
- 2. Create your feature branch (`git checkout -b my-new-feature`)
19
- 3. Commit your changes (`git commit -am 'Add some feature'`)
20
- 4. Push to the branch (`git push origin my-new-feature`)
21
- 5. Create a new Pull Request
data/Gemfile DELETED
@@ -1,8 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in bootsnap.gemspec
4
- gemspec
5
-
6
- group :development do
7
- gem 'rubocop'
8
- end
data/README.jp.md DELETED
@@ -1,231 +0,0 @@
1
- # Bootsnap [![Build Status](https://travis-ci.org/Shopify/bootsnap.svg?branch=master)](https://travis-ci.org/Shopify/bootsnap)
2
-
3
- Bootsnap は RubyVM におけるバイトコード生成やファイルルックアップ等の時間のかかる処理を最適化するためのライブラリです。ActiveSupport や YAML もサポートしています。[内部動作](#内部動作)もご覧ください。
4
-
5
- 注意書き: このライブラリは英語話者によって管理されています。この README は日本語ですが、日本語でのサポートはしておらず、リクエストにお答えすることもできません。バイリンガルの方がサポートをサポートしてくださる場合はお知らせください!:)
6
-
7
- ### パフォーマンス
8
-
9
- * [Discourse](https://github.com/discourse/discourse) では、約6秒から3秒まで、約50%の起動時間短縮が確認されています。
10
- * 小さなアプリケーションでも、50%の改善(3.6秒から1.8秒)が確認されています。
11
- * 非常に巨大でモノリシックなアプリである Shopify のプラットフォームでは、約25秒から6.5秒へと約75%短縮されました。
12
-
13
- ## 使用方法
14
-
15
- この gem は macOS と Linux で作動します。まずは、`bootsnap` を `Gemfile` に追加します:
16
-
17
- ```ruby
18
- gem 'bootsnap', require: false
19
- ```
20
-
21
- Rails を使用している場合は、以下のコードを、`config/boot.rb` 内にある `require 'bundler/setup'` の直後に追加してください。
22
-
23
- ```ruby
24
- require 'bootsnap/setup'
25
- ```
26
-
27
- 単に `gem 'bootsnap', require: 'bootsnap/setup'` と指定することも技術的には可能ですが、最大限のパフォーマンス改善を得るためには Bootsnap をできるだけ早く読み込むことが重要です。
28
-
29
- この require の仕組みは[こちら](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb)で確認できます。
30
-
31
- Rails を使用していない場合、または、より多くの設定を変更したい場合は、以下のコードを `require 'bundler/setup'` の直後に追加してください(早く読み込まれるほど、より多くのものを最適化することができます)。
32
-
33
- ```ruby
34
- require 'bootsnap'
35
- env = ENV['RAILS_ENV'] || "development"
36
- Bootsnap.setup(
37
-  cache_dir:           'tmp/cache',         # キャッシュファイルを保存する path
38
-  development_mode:     env == 'development', # 現在の作業環境、例えば RACK_ENV, RAILS_ENV など。
39
- load_path_cache: true, # キャッシュで LOAD_PATH を最適化する。
40
-  autoload_paths_cache: true,                 # キャッシュで ActiveSupport による autoload を行う。
41
-  disable_trace:       true,                 # (アルファ) `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`をセットする。
42
-  compile_cache_iseq:   true,                 # ISeq キャッシュをコンパイルする
43
-  compile_cache_yaml:   true                 # YAML キャッシュをコンパイルする
44
- )
45
- ```
46
-
47
- **ヒント**: `require 'bootsnap'` を `BootLib::Require.from_gem('bootsnap', 'bootsnap')` で、 [こちらのトリック](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require)を使って置き換えることができます。こうすると、巨大な`$LOAD_PATH`がある場合でも、起動時間を最短化するのに役立ちます。
48
-
49
- 注意: Bootsnap と [Spring](https://github.com/rails/spring) は別領域の問題を扱うツールです。Bootsnap は個々のソースファイルの読み込みを高速化します。一方で、Spring は起動されたRailsプロセスのコピーを保持して次回の起動時に起動プロセスの一部を完全にスキップします。2つのツールはうまく連携しており、どちらも新しく生成された Rails アプリケーションにデフォルトで含まれています。
50
-
51
- ### 環境
52
- Bootsnapのすべての機能はセットアップ時の設定に従って開発、テスト、プロダクション、および他のすべての環境で有効化されます。Shopify では、この gem を問題なくすべての環境で安全に使用しています。
53
-
54
- 特定の環境で機能を無効にする場合は、必要に応じて適切な ENV 変数または設定を考慮して設定を変更することをおすすめします。
55
-
56
- ## 内部動作
57
-
58
- Bootsnap は、処理に時間のかかるメソッドの結果をキャッシュすることで最適化しています。これは、大きく分けて2つのカテゴリに分けられます。
59
-
60
- * [Path Pre-Scanning](#path-pre-scanning)
61
- * `Kernel#require` と `Kernel#load` を `$LOAD_PATH` フルスキャンを行わないように変更します。
62
- * `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` を `ActiveSupport::Dependencies.autoload_paths` のフルスキャンを行わないようにオーバーライドします。
63
- * [Compilation caching](#compilation-caching)
64
- * Ruby バイトコードのコンパイル結果をキャッシュするためのメソッド `RubyVM::InstructionSequence.load_iseq` が実装されています。
65
- * `YAML.load_file` を YAML オブジェクトのロード結果を MessagePack でキャッシュするように変更します。 MessagePack でサポートされていないタイプが使われている場合は Marshal が使われます。
66
-
67
- ### Path Pre-Scanning
68
-
69
- _(このライブラリは [bootscale](https://github.com/byroot/bootscale) という別のライブラリを元に開発されました)_
70
-
71
- Bootsnap の初期化時、あるいはパス(例えば、`$LOAD_PATH`)の変更時に、`Bootsnap::LoadPathCache` がキャッシュから必要なエントリーのリストを読み込みます。または、必要に応じてフルスキャンを実行し結果をキャッシュします。
72
- その後、たとえば `require 'foo'` を評価する場合, Ruby は `$LOAD_PATH` `['x', 'y', ...]` のすべてのエントリーを繰り返し評価することで `x/foo.rb`, `y/foo.rb` などを探索します。これに対して Bootsnap は、キャッシュされた require 可能なファイルと `$LOAD_PATH` を見ることで、Rubyが最終的に選択するであろうパスで置き換えます。
73
-
74
- この動作によって生成された syscall を見ると、最終的な結果は以前なら次のようになります。
75
-
76
- ```
77
- open x/foo.rb # (fail)
78
- # (imagine this with 500 $LOAD_PATH entries instead of two)
79
- open y/foo.rb # (success)
80
- close y/foo.rb
81
- open y/foo.rb
82
- ...
83
- ```
84
-
85
- これが、次のようになります:
86
-
87
- ```
88
- open y/foo.rb
89
- ...
90
- ```
91
-
92
- `autoload_paths_cache` オプションが `Bootsnap.setup` に与えられている場合、`ActiveSupport::Dependencies.autoload_paths` をトラバースする方法にはまったく同じ最適化が使用されます。
93
-
94
- `*_path_cache` を機能させるオーバーライドを図にすると、次のようになります。
95
-
96
- ![Bootsnapの説明図](https://cloud.githubusercontent.com/assets/3074765/24532120/eed94e64-158b-11e7-9137-438d759b2ac8.png)
97
-
98
- Bootsnap は、 `$LOAD_PATH` エントリを安定エントリと不安定エントリの2つのカテゴリに分類します。不安定エントリはアプリケーションが起動するたびにスキャンされ、そのキャッシュは30秒間だけ有効になります。安定エントリーに期限切れはありません。コンテンツがスキャンされると、決して変更されないものとみなされます。
99
-
100
- 安定していると考えられる唯一のディレクトリは、Rubyのインストールプレフィックス (`RbConfig::CONFIG['prefix']`, または `/usr/local/ruby` や `~/.rubies/x.y.z`)下にあるものと、`Gem.path` (たとえば `~/.gem/ruby/x.y.z`) や `Bundler.bundle_path` 下にあるものです。他のすべては不安定エントリと分類されます。
101
-
102
- [`Bootsnap::LoadPathCache::Cache`](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/cache.rb) に加えて次の図では、エントリの解決がどのように機能するかを理解するのに役立つかもしれません。経路探索は以下のようになります。
103
-
104
- ![パス探索の仕組み](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png)
105
-
106
- また、`LoadError` のスキャンがどれほど重いかに注意を払うことも大切です。もし Ruby が `require 'something'` を評価し、そのファイルが `$LOAD_PATH` にない場合は、それを知るために `2 * $LOAD_PATH.length` のファイルシステムアスセスが必要になります。Bootsnap は、ファイルシステムにまったく触れずに `LoadError` を投げ、この結果をキャッシュします。
107
-
108
- ## Compilation Caching
109
-
110
- *(このコンセプトのより分かりやすい解説は [yomikomu](https://github.com/ko1/yomikomu) をお読み下さい。)*
111
-
112
- Ruby には複雑な文法が実装されており、構文解析は簡単なオペレーションではありません。1.9以降、Ruby は Ruby ソースを内部のバイトコードに変換した後、Ruby VM によって実行してきました。2.3.0 以降、[RubyはAPIを公開し](https://ruby-doc.org/core-2.3.0/RubyVM/InstructionSequence.html)、そのバイトコードをキャッシュすることができるようになりました。これにより、同じファイルが複数ロードされた時の、比較的時間のかかる部分をバイパスすることができます。
113
-
114
- また、アプリケーションの起動時に YAML ドキュメントの読み込みに多くの時間を費やしていることを発見しました。そして、 MessagePack と Marshal は deserialization にあたって YAML よりもはるかに高速であるということに気付きました。そこで、YAML ドキュメントを、Ruby バイトコードと同じコンパイルキャッシングの最適化を施すことで、高速化しています。Ruby の "バイトコード" フォーマットに相当するものは MessagePack ドキュメント (あるいは、MessagePack をサポートしていないタイプの YAML ドキュメントの場合は、Marshal stream)になります。
115
-
116
- これらのコンパイル結果は、入力ファイル(FNV1a-64)のフルパスのハッシュを取って生成されたファイル名で、キャッシュディレクトリに保存されます。
117
-
118
- Bootsnap 無しでは、ファイルを `require` するために生成された syscall の順序は次のようになっていました:
119
-
120
- ```
121
- open /c/foo.rb -> m
122
- fstat64 m
123
- close m
124
- open /c/foo.rb -> o
125
- fstat64 o
126
- fstat64 o
127
- read o
128
- read o
129
- ...
130
- close o
131
- ```
132
-
133
- しかし Bootsnap では、次のようになります:
134
-
135
- ```
136
- open /c/foo.rb -> n
137
- fstat64 n
138
- close n
139
- open /c/foo.rb -> n
140
- fstat64 n
141
- open (cache) -> m
142
- read m
143
- read m
144
- close m
145
- close n
146
- ```
147
-
148
- これは一見劣化していると思われるかもしれませんが、性能に大きな違いがあります。
149
-
150
- *(両方のリストの最初の3つの syscalls -- `open`, `fstat64`, `close` -- は本質的に有用ではありません。[このRubyパッチ](https://bugs.ruby-lang.org/issues/13378)は、Boosnap と組み合わせることによって、それらを最適化しています)*
151
-
152
- Bootsnap は、64バイトのヘッダーとそれに続くキャッシュの内容を含んだキャッシュファイルを書き込みます。ヘッダーは、次のいくつかのフィールドで構成されるキャッシュキーです。
153
-
154
- - `version`、Bootsnapにハードコードされる基本的なスキーマのバージョン
155
- - `os_version`、(macOS, BSDの) 現在のカーネルバージョンか 、(Linuxの) glibc のバージョンのハッシュ
156
- - `compile_option`、`RubyVM::InstructionSequence.compile_option` の返り値
157
- - `ruby_revision`、コンパイルされたRubyのバージョン
158
- - `size`、ソースファイルのサイズ
159
- - `mtime`、コンパイル時のソースファイルの最終変更タイムスタンプ
160
- - `data_size`、バッファに読み込む必要のあるヘッダーに続くバイト数。
161
-
162
- キーが有効な場合、キャッシュがファイルからロードされます。そうでない場合、キャッシュは再生成され、現在のキャッシュを破棄します。
163
-
164
- # 最終的なキャッシュ結果
165
-
166
- 次のファイル構造があるとします。
167
-
168
- ```
169
- /
170
- ├── a
171
- ├── b
172
- └── c
173
- └── foo.rb
174
- ```
175
-
176
- そして、このような `$LOAD_PATH` があるとします。
177
-
178
- ```
179
- ["/a", "/b", "/c"]
180
- ```
181
-
182
- Bootsnap なしで `require 'foo'` を呼び出すと、Ruby は次の順序で syscalls を生成します:
183
-
184
- ```
185
- open /a/foo.rb -> -1
186
- open /b/foo.rb -> -1
187
- open /c/foo.rb -> n
188
- close n
189
- open /c/foo.rb -> m
190
- fstat64 m
191
- close m
192
- open /c/foo.rb -> o
193
- fstat64 o
194
- fstat64 o
195
- read o
196
- read o
197
- ...
198
- close o
199
- ```
200
-
201
- しかし Bootsnap では、次のようになります:
202
-
203
- ```
204
- open /c/foo.rb -> n
205
- fstat64 n
206
- close n
207
- open /c/foo.rb -> n
208
- fstat64 n
209
- open (cache) -> m
210
- read m
211
- read m
212
- close m
213
- close n
214
- ```
215
-
216
- Bootsnap なしで `require 'nope'` を呼び出すと、次のようになります:
217
-
218
- ```
219
- open /a/nope.rb -> -1
220
- open /b/nope.rb -> -1
221
- open /c/nope.rb -> -1
222
- open /a/nope.bundle -> -1
223
- open /b/nope.bundle -> -1
224
- open /c/nope.bundle -> -1
225
- ```
226
-
227
- ...そして、Bootsnap で `require 'nope'` を呼び出すと、次のようになります...
228
-
229
- ```
230
- # (nothing!)
231
- ```