bootsnap 1.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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