bootsnap 1.1.0-java

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.
@@ -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
@@ -0,0 +1,17 @@
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-*
@@ -0,0 +1,7 @@
1
+ inherit_from:
2
+ - http://shopify.github.io/ruby-style-guide/rubocop.yml
3
+
4
+ AllCops:
5
+ Exclude:
6
+ - 'vendor/**/*'
7
+
@@ -0,0 +1,5 @@
1
+ os: osx
2
+ language: ruby
3
+ rvm: ruby-2.4.0
4
+ before_script: rake
5
+ script: bin/testunit
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bootsnap.gemspec
4
+ gemspec
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.
@@ -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 |
@@ -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)
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ if [ $# -eq 0 ]; then
4
+ ruby -I"test" -e 'Dir.glob("./test/**/*_test.rb").each { |f| require f }' -- "$@"
5
+ else
6
+ path=$1
7
+ ruby -I"test" -e "require '${path#test/}'" -- "$@"
8
+ fi