bootsnap 1.1.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +7 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +31 -0
- data/CONTRIBUTING.md +21 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +284 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/testunit +8 -0
- data/bootsnap.gemspec +39 -0
- data/dev.yml +8 -0
- data/ext/bootsnap/bootsnap.c +742 -0
- data/ext/bootsnap/bootsnap.h +6 -0
- data/ext/bootsnap/extconf.rb +17 -0
- data/lib/bootsnap.rb +39 -0
- data/lib/bootsnap/compile_cache.rb +15 -0
- data/lib/bootsnap/compile_cache/iseq.rb +71 -0
- data/lib/bootsnap/compile_cache/yaml.rb +57 -0
- data/lib/bootsnap/explicit_require.rb +44 -0
- data/lib/bootsnap/load_path_cache.rb +52 -0
- data/lib/bootsnap/load_path_cache/cache.rb +191 -0
- data/lib/bootsnap/load_path_cache/change_observer.rb +56 -0
- data/lib/bootsnap/load_path_cache/core_ext/active_support.rb +73 -0
- data/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb +88 -0
- data/lib/bootsnap/load_path_cache/path.rb +113 -0
- data/lib/bootsnap/load_path_cache/path_scanner.rb +42 -0
- data/lib/bootsnap/load_path_cache/store.rb +77 -0
- data/lib/bootsnap/setup.rb +47 -0
- data/lib/bootsnap/version.rb +3 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 23456864a83d7cb5c0628ac3b4ab63ed73012bf1
|
4
|
+
data.tar.gz: 9abcf27c44220fd9d3330ee154c6a39f3e4854d2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6661871e089bf414b5e828408eeddb2def5acb8d88d9d1cb2692140768ea9891ff1c5e0011544a935105dcdfd0e4a863e6060156404e30b73a3613bb6a493256
|
7
|
+
data.tar.gz: b1a51caa8730f9be7f16e6a67b308b824f1fcc4817f933abebf8bf1b479cacad3ab66bc9b11e6ab91043eb79d1b60f736f0265f5e0d750db16b63e759df7eef1
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# 1.1.0
|
2
|
+
|
3
|
+
* Add `bootsnap/setup`
|
4
|
+
* Support jruby (without compile caching features)
|
5
|
+
* Better deoptimization when Coverage is enabled
|
6
|
+
* Consider `Bundler.bundle_path` to be stable
|
7
|
+
|
8
|
+
# 1.0.0
|
9
|
+
|
10
|
+
* (none)
|
11
|
+
|
12
|
+
# 0.3.2
|
13
|
+
|
14
|
+
* Minor performance savings around checking validity of cache in the presence of relative paths.
|
15
|
+
* When coverage is enabled, skips optimization instead of exploding.
|
16
|
+
|
17
|
+
# 0.3.1
|
18
|
+
|
19
|
+
* Don't whitelist paths under `RbConfig::CONFIG['prefix']` as stable; instead use `['libdir']` (#41).
|
20
|
+
* Catch `EOFError` when reading load-path-cache and regenerate cache.
|
21
|
+
* Support relative paths in load-path-cache.
|
22
|
+
|
23
|
+
# 0.3.0
|
24
|
+
|
25
|
+
* Migrate CompileCache from xattr as a cache backend to a cache directory
|
26
|
+
* Adds support for Linux and FreeBSD
|
27
|
+
|
28
|
+
# 0.2.15
|
29
|
+
|
30
|
+
* Support more versions of ActiveSupport (`depend_on`'s signature varies; don't reiterate it)
|
31
|
+
* Fix bug in handling autoloaded modules that raise NoMethodError
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,21 @@
|
|
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
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2017 Shopify
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,284 @@
|
|
1
|
+
# Bootsnap [![Build Status](https://travis-ci.org/Shopify/bootsnap.svg?branch=master)](https://travis-ci.org/Shopify/bootsnap)
|
2
|
+
|
3
|
+
**Beta-quality. See [the last section of this README](#trustworthiness).**
|
4
|
+
|
5
|
+
Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSupport` and `YAML`,
|
6
|
+
to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work).
|
7
|
+
|
8
|
+
#### Performance
|
9
|
+
- [Discourse](https://github.com/discourse/discourse) reports a boot time reduction of approximately 50%, from roughly 6 to 3 seconds on one machine;
|
10
|
+
- One of our smaller internal apps also sees a reduction of 50%, from 3.6 to 1.8 seconds;
|
11
|
+
- The core Shopify platform -- a rather large monolithic application -- boots about 75% faster, dropping from around 25s to 6.5s.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
This gem works on MacOS and Linux.
|
16
|
+
|
17
|
+
Add `bootsnap` to your `Gemfile`:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'bootsnap'
|
21
|
+
```
|
22
|
+
|
23
|
+
If you are using rails, add this to `config/boot.rb` immediately after `require 'bundler/setup'`:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'bootsnap/setup'
|
27
|
+
```
|
28
|
+
|
29
|
+
If you are not using rails, or if you are but want more control over things, add this to your
|
30
|
+
application setup immediately after `require 'bundler/setup'` (i.e. as early as possible: the sooner
|
31
|
+
this is loaded, the sooner it can start optimizing things)
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'bootsnap'
|
35
|
+
env = ENV['RAILS_ENV'] || "development"
|
36
|
+
Bootsnap.setup(
|
37
|
+
cache_dir: 'tmp/cache', # Path to your cache
|
38
|
+
development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc
|
39
|
+
load_path_cache: true, # Optimize the LOAD_PATH with a cache
|
40
|
+
autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache
|
41
|
+
disable_trace: true, # (Alpha) Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`
|
42
|
+
compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting.
|
43
|
+
compile_cache_yaml: true # Compile YAML into a cache
|
44
|
+
)
|
45
|
+
```
|
46
|
+
|
47
|
+
**Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap',
|
48
|
+
'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This
|
49
|
+
will help optimize boot time further if you have an extremely large `$LOAD_PATH`.
|
50
|
+
|
51
|
+
## How does this work?
|
52
|
+
|
53
|
+
Bootsnap optimizes methods to cache results of expensive computations, and can be grouped
|
54
|
+
into two broad categories:
|
55
|
+
|
56
|
+
* [Path Pre-Scanning](#path-pre-scanning)
|
57
|
+
* `Kernel#require` and `Kernel#load` are modified to eliminate `$LOAD_PATH` scans.
|
58
|
+
* `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` are
|
59
|
+
overridden to eliminate scans of `ActiveSupport::Dependencies.autoload_paths`.
|
60
|
+
* [Compilation caching](#compilation-caching)
|
61
|
+
* `RubyVM::InstructionSequence.load_iseq` is implemented to cache the result of ruby bytecode
|
62
|
+
compilation.
|
63
|
+
* `YAML.load_file` is modified to cache the result of loading a YAML object in MessagePack format
|
64
|
+
(or Marshal, if the message uses types unsupported by MessagePack).
|
65
|
+
|
66
|
+
### Path Pre-Scanning
|
67
|
+
|
68
|
+
*(This work is a minor evolution of [bootscale](https://github.com/byroot/bootscale)).*
|
69
|
+
|
70
|
+
Upon initialization of bootsnap or modification of the path (e.g. `$LOAD_PATH`),
|
71
|
+
`Bootsnap::LoadPathCache` will fetch a list of requirable entries from a cache, or, if necessary,
|
72
|
+
perform a full scan and cache the result.
|
73
|
+
|
74
|
+
Later, when we run (e.g.) `require 'foo'`, ruby *would* iterate through every item on our
|
75
|
+
`$LOAD_PATH` `['x', 'y', ...]`, looking for `x/foo.rb`, `y/foo.rb`, and so on. Bootsnap instead
|
76
|
+
looks at all the cached requirables for each `$LOAD_PATH` entry and substitutes the full expanded
|
77
|
+
path of the match ruby would have eventually chosen.
|
78
|
+
|
79
|
+
If you look at the syscalls generated by this behaviour, the net effect is that what would
|
80
|
+
previously look like this:
|
81
|
+
|
82
|
+
```
|
83
|
+
open x/foo.rb # (fail)
|
84
|
+
# (imagine this with 500 $LOAD_PATH entries instead of two)
|
85
|
+
open y/foo.rb # (success)
|
86
|
+
close y/foo.rb
|
87
|
+
open y/foo.rb
|
88
|
+
...
|
89
|
+
```
|
90
|
+
|
91
|
+
becomes this:
|
92
|
+
|
93
|
+
```
|
94
|
+
open y/foo.rb
|
95
|
+
...
|
96
|
+
```
|
97
|
+
|
98
|
+
Exactly the same strategy is employed for methods that traverse
|
99
|
+
`ActiveSupport::Dependencies.autoload_paths` if the `autoload_paths_cache` option is given to
|
100
|
+
`Bootsnap.setup`.
|
101
|
+
|
102
|
+
The following diagram flowcharts the overrides that make the `*_path_cache` features work.
|
103
|
+
|
104
|
+
![Flowchart explaining
|
105
|
+
Bootsnap](https://cloud.githubusercontent.com/assets/3074765/24532120/eed94e64-158b-11e7-9137-438d759b2ac8.png)
|
106
|
+
|
107
|
+
Bootsnap classifies path entries into two categories: stable and volatile. Volatile entries are
|
108
|
+
scanned each time the application boots, and their caches are only valid for 30 seconds. Stable
|
109
|
+
entries do not expire -- once their contents has been scanned, it is assumed to never change.
|
110
|
+
|
111
|
+
The only directories considered "stable" are things under the Ruby install prefix
|
112
|
+
(`RbConfig::CONFIG['prefix']`, e.g. `/usr/local/ruby` or `~/.rubies/x.y.z`), and things under the
|
113
|
+
`Gem.path` (e.g. `~/.gem/ruby/x.y.z`) or `Bundler.bundle_path`. Everything else is considered
|
114
|
+
"volatile".
|
115
|
+
|
116
|
+
In addition to the [`Bootsnap::LoadPathCache::Cache`
|
117
|
+
source](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/cache.rb),
|
118
|
+
this diagram may help clarify how entry resolution works:
|
119
|
+
|
120
|
+
![How path searching works](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png)
|
121
|
+
|
122
|
+
|
123
|
+
It's also important to note how expensive `LoadError`s can be. If ruby invokes
|
124
|
+
`require 'something'`, but that file isn't on `$LOAD_PATH`, it takes `2 *
|
125
|
+
$LOAD_PATH.length` filesystem accesses to determine that. Bootsnap caches this
|
126
|
+
result too, raising a `LoadError` without touching the filesystem at all.
|
127
|
+
|
128
|
+
### Compilation Caching
|
129
|
+
|
130
|
+
*(A more readable implementation of this concept can be found in
|
131
|
+
[yomikomu](https://github.com/ko1/yomikomu)).*
|
132
|
+
|
133
|
+
Ruby has complex grammar and parsing it is not a particularly cheap operation. Since 1.9, Ruby has
|
134
|
+
translated ruby source to an internal bytecode format, which is then executed by the Ruby VM. Since
|
135
|
+
2.2, Ruby [exposes an API](https://ruby-doc.org/core-2.3.0/RubyVM/InstructionSequence.html) that
|
136
|
+
allows caching that bytecode. This allows us to bypass the relatively-expensive compilation step on
|
137
|
+
subsequent loads of the same file.
|
138
|
+
|
139
|
+
We also noticed that we spend a lot of time loading YAML documents during our application boot, and
|
140
|
+
that MessagePack and Marshal are *much* faster at deserialization than YAML, even with a fast
|
141
|
+
implementation. We use the same strategy of compilation caching for YAML documents, with the
|
142
|
+
equivalent of Ruby's "bytecode" format being a MessagePack document (or, in the case of YAML
|
143
|
+
documents with types unsupported by MessagePack, a Marshal stream).
|
144
|
+
|
145
|
+
These compilation results are stored in a cache directory, with filenames generated by taking a hash
|
146
|
+
of the full expanded path of the input file (FNV1a-64).
|
147
|
+
|
148
|
+
Whereas before, the sequence of syscalls generated to `require` a file would look like:
|
149
|
+
|
150
|
+
```
|
151
|
+
open /c/foo.rb -> m
|
152
|
+
fstat64 m
|
153
|
+
close m
|
154
|
+
open /c/foo.rb -> o
|
155
|
+
fstat64 o
|
156
|
+
fstat64 o
|
157
|
+
read o
|
158
|
+
read o
|
159
|
+
...
|
160
|
+
close o
|
161
|
+
```
|
162
|
+
|
163
|
+
With bootsnap, we get:
|
164
|
+
|
165
|
+
```
|
166
|
+
open /c/foo.rb -> n
|
167
|
+
fstat64 n
|
168
|
+
close n
|
169
|
+
open /c/foo.rb -> n
|
170
|
+
fstat64 n
|
171
|
+
open (cache) -> m
|
172
|
+
read m
|
173
|
+
read m
|
174
|
+
close m
|
175
|
+
close n
|
176
|
+
```
|
177
|
+
|
178
|
+
This may look worse at a glance, but underlies a large performance difference.
|
179
|
+
|
180
|
+
*(The first three syscalls in both listings -- `open`, `fstat64`, `close` -- are not inherently
|
181
|
+
useful. [This ruby patch](https://bugs.ruby-lang.org/issues/13378) optimizes them out when coupled
|
182
|
+
with bootsnap.)*
|
183
|
+
|
184
|
+
Bootsnap writes a cache file containing a 64 byte header followed by the cache contents. The header
|
185
|
+
is a cache key including several fields:
|
186
|
+
|
187
|
+
* `version`, hardcoded in bootsnap. Essentially a schema version;
|
188
|
+
* `os_version`, A hash of the current kernel version (on macOS, BSD) or glibc version (on Linux);
|
189
|
+
* `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does;
|
190
|
+
* `ruby_revision`, the version of Ruby this was compiled with;
|
191
|
+
* `size`, the size of the source file;
|
192
|
+
* `mtime`, the last-modification timestamp of the source file when it was compiled; and
|
193
|
+
* `data_size`, the number of bytes following the header, which we need to read it into a buffer.
|
194
|
+
|
195
|
+
If the key is valid, the result is loaded from the value. Otherwise, it is regenerated and clobbers
|
196
|
+
the current cache.
|
197
|
+
|
198
|
+
### Putting it all together
|
199
|
+
|
200
|
+
Imagine we have this file structure:
|
201
|
+
|
202
|
+
```
|
203
|
+
/
|
204
|
+
├── a
|
205
|
+
├── b
|
206
|
+
└── c
|
207
|
+
└── foo.rb
|
208
|
+
```
|
209
|
+
|
210
|
+
And this `$LOAD_PATH`:
|
211
|
+
|
212
|
+
```
|
213
|
+
["/a", "/b", "/c"]
|
214
|
+
```
|
215
|
+
|
216
|
+
When we call `require 'foo'` without bootsnap, Ruby would generate this sequence of syscalls:
|
217
|
+
|
218
|
+
|
219
|
+
```
|
220
|
+
open /a/foo.rb -> -1
|
221
|
+
open /b/foo.rb -> -1
|
222
|
+
open /c/foo.rb -> n
|
223
|
+
close n
|
224
|
+
open /c/foo.rb -> m
|
225
|
+
fstat64 m
|
226
|
+
close m
|
227
|
+
open /c/foo.rb -> o
|
228
|
+
fstat64 o
|
229
|
+
fstat64 o
|
230
|
+
read o
|
231
|
+
read o
|
232
|
+
...
|
233
|
+
close o
|
234
|
+
```
|
235
|
+
|
236
|
+
With bootsnap, we get:
|
237
|
+
|
238
|
+
```
|
239
|
+
open /c/foo.rb -> n
|
240
|
+
fstat64 n
|
241
|
+
close n
|
242
|
+
open /c/foo.rb -> n
|
243
|
+
fstat64 n
|
244
|
+
open (cache) -> m
|
245
|
+
read m
|
246
|
+
read m
|
247
|
+
close m
|
248
|
+
close n
|
249
|
+
```
|
250
|
+
|
251
|
+
If we call `require 'nope'` without bootsnap, we get:
|
252
|
+
|
253
|
+
```
|
254
|
+
open /a/nope.rb -> -1
|
255
|
+
open /b/nope.rb -> -1
|
256
|
+
open /c/nope.rb -> -1
|
257
|
+
open /a/nope.bundle -> -1
|
258
|
+
open /b/nope.bundle -> -1
|
259
|
+
open /c/nope.bundle -> -1
|
260
|
+
```
|
261
|
+
|
262
|
+
...and if we call `require 'nope'` *with* bootsnap, we get...
|
263
|
+
|
264
|
+
```
|
265
|
+
# (nothing!)
|
266
|
+
```
|
267
|
+
|
268
|
+
## Trustworthiness
|
269
|
+
|
270
|
+
We use the `*_path_cache` features in production and haven't experienced any issues in a long time.
|
271
|
+
|
272
|
+
The `compile_cache_*` features work well for us in development on macOS. It should work on Linux,
|
273
|
+
and we intend to deploy it in production, but we haven't yet.
|
274
|
+
|
275
|
+
`disable_trace` should be completely safe, but we don't really use it because some people like to
|
276
|
+
use tools that make use of `trace` instructions.
|
277
|
+
|
278
|
+
| feature | where we're using it |
|
279
|
+
|-|-|
|
280
|
+
| `load_path_cache` | everywhere |
|
281
|
+
| `autoload_path_cache` | everywhere |
|
282
|
+
| `disable_trace` | nowhere, but it's safe unless you need tracing |
|
283
|
+
| `compile_cache_iseq` | development, but probably safe to use everywhere |
|
284
|
+
| `compile_cache_yaml` | development, but probably safe to use everywhere |
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rake/extensiontask'
|
2
|
+
|
3
|
+
gemspec = Gem::Specification.load('bootsnap.gemspec')
|
4
|
+
Rake::ExtensionTask.new do |ext|
|
5
|
+
ext.name = 'bootsnap'
|
6
|
+
ext.ext_dir = 'ext/bootsnap'
|
7
|
+
ext.lib_dir = 'lib/bootsnap'
|
8
|
+
ext.gem_spec = gemspec
|
9
|
+
end
|
10
|
+
|
11
|
+
task(default: :compile)
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "bootsnap"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED