faster_path 0.3.10 → 0.5.0
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.
- checksums.yaml +4 -4
- data/Cargo.lock +19 -37
- data/Cargo.toml +5 -9
- data/Gemfile +3 -7
- data/README.md +114 -34
- data/Rakefile +17 -12
- data/ext/Rakefile +12 -6
- data/faster_path.gemspec +10 -6
- data/lib/faster_path/build_task.rb +32 -0
- data/lib/faster_path/optional/monkeypatches.rb +2 -2
- data/lib/faster_path/optional/refinements.rb +2 -2
- data/lib/faster_path/version.rb +1 -1
- data/lib/faster_path.rb +10 -13
- data/src/basename.rs +166 -69
- data/src/chop_basename.rs +84 -21
- data/src/cleanpath_aggressive.rs +87 -71
- data/src/cleanpath_conservative.rs +175 -79
- data/src/dirname.rs +96 -32
- data/src/extname.rs +150 -20
- data/src/lib.rs +54 -104
- data/src/path_parsing.rs +282 -21
- data/src/pathname.rs +332 -252
- data/src/plus.rs +90 -74
- data/src/prepend_prefix.rs +11 -13
- data/src/relative_path_from.rs +72 -48
- data/src/ruby.rs +530 -0
- metadata +36 -40
- data/lib/faster_path/thermite_initialize.rb +0 -22
- data/src/debug.rs +0 -29
- data/src/helpers.rs +0 -51
- data/src/memrnchr.rs +0 -70
- data/src/pathname_sys.rs +0 -41
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fb5780bd8ce8ea17d538b540ac69b1c023118d1351e167cde418ee48a4f8085
|
|
4
|
+
data.tar.gz: b12bc7a5ab8e4e26cfd46328bdb9e4b57eea367a9c40e76aeba74e12f06eb0e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa615d0278378e74432832369cf56b16147ecc0e5e27bd12b1fd90c2690fab41dc68fd3042e8dd7c92b8ebb7741f7d08e9db25bd0cec61b384b520f9bd8b400a
|
|
7
|
+
data.tar.gz: 905389671e4062ef0824e7fb7af8d05fefb1df0ad2a5919bc8a54a8506309ba77cea577859ea49da4e15616fd2fb4bf1d025e5082baef6e61bf18c0c9799d000
|
data/Cargo.lock
CHANGED
|
@@ -1,57 +1,39 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
1
5
|
[[package]]
|
|
2
6
|
name = "faster_path"
|
|
3
|
-
version = "0.0
|
|
7
|
+
version = "0.5.0"
|
|
4
8
|
dependencies = [
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"ruby-sys 0.3.0 (git+https://github.com/danielpclark/ruby-sys?branch=playground)",
|
|
8
|
-
"ruru 0.9.3 (git+https://github.com/danielpclark/ruru?branch=playground)",
|
|
9
|
+
"memchr",
|
|
10
|
+
"rutie",
|
|
9
11
|
]
|
|
10
12
|
|
|
11
13
|
[[package]]
|
|
12
14
|
name = "lazy_static"
|
|
13
|
-
version = "
|
|
14
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
-
|
|
16
|
-
[[package]]
|
|
17
|
-
name = "lazy_static"
|
|
18
|
-
version = "1.0.0"
|
|
15
|
+
version = "1.5.1"
|
|
19
16
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
17
|
+
checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
|
20
18
|
|
|
21
19
|
[[package]]
|
|
22
20
|
name = "libc"
|
|
23
|
-
version = "0.2.
|
|
21
|
+
version = "0.2.189"
|
|
24
22
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
23
|
+
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
25
24
|
|
|
26
25
|
[[package]]
|
|
27
26
|
name = "memchr"
|
|
28
|
-
version = "2.
|
|
27
|
+
version = "2.8.3"
|
|
29
28
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
30
|
-
|
|
31
|
-
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
32
|
-
]
|
|
29
|
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
33
30
|
|
|
34
31
|
[[package]]
|
|
35
|
-
name = "
|
|
36
|
-
version = "0.
|
|
37
|
-
source = "
|
|
38
|
-
|
|
39
|
-
"libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
[[package]]
|
|
43
|
-
name = "ruru"
|
|
44
|
-
version = "0.9.3"
|
|
45
|
-
source = "git+https://github.com/danielpclark/ruru?branch=playground#ab84ad4b97c41bfe6fd6d0f8540b2efd77583561"
|
|
32
|
+
name = "rutie"
|
|
33
|
+
version = "0.13.1"
|
|
34
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
35
|
+
checksum = "30d1529470ad0b69b81a6d0224632d00c01b46ef671452e24407512165b7ed84"
|
|
46
36
|
dependencies = [
|
|
47
|
-
"lazy_static
|
|
48
|
-
"
|
|
37
|
+
"lazy_static",
|
|
38
|
+
"libc",
|
|
49
39
|
]
|
|
50
|
-
|
|
51
|
-
[metadata]
|
|
52
|
-
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
|
|
53
|
-
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
|
54
|
-
"checksum libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)" = "f54263ad99207254cf58b5f701ecb432c717445ea2ee8af387334bdd1a03fdff"
|
|
55
|
-
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
|
56
|
-
"checksum ruby-sys 0.3.0 (git+https://github.com/danielpclark/ruby-sys?branch=playground)" = "<none>"
|
|
57
|
-
"checksum ruru 0.9.3 (git+https://github.com/danielpclark/ruru?branch=playground)" = "<none>"
|
data/Cargo.toml
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "faster_path"
|
|
3
|
-
version = "0.0
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
authors = ["Daniel P. Clark <6ftdan@gmail.com>"]
|
|
5
5
|
description = "Alternative to Pathname"
|
|
6
6
|
homepage = "https://github.com/danielpclark/faster_path"
|
|
7
7
|
repository = "https://github.com/danielpclark/faster_path"
|
|
8
8
|
license = "MIT OR Apache-2.0"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
|
|
11
|
-
[package.metadata.thermite]
|
|
12
|
-
github_releases = true
|
|
10
|
+
edition = "2021"
|
|
13
11
|
|
|
14
12
|
[lib]
|
|
15
13
|
name = "faster_path"
|
|
16
|
-
crate-type = ["
|
|
14
|
+
crate-type = ["cdylib"]
|
|
17
15
|
|
|
18
16
|
[dependencies]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
lazy_static = "1.0"
|
|
22
|
-
memchr = "2.0.1"
|
|
17
|
+
rutie = "0.13.1"
|
|
18
|
+
memchr = "2"
|
data/Gemfile
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
source 'https://rubygems.org'
|
|
2
|
-
# Specify your gem's dependencies in faster_path.gemspec
|
|
3
|
-
gemspec
|
|
1
|
+
source 'https://rubygems.org'
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
end
|
|
8
|
-
end
|
|
3
|
+
# Specify your gem's dependencies in faster_path.gemspec
|
|
4
|
+
gemspec
|
data/README.md
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# FasterPath
|
|
2
2
|
[](https://badge.fury.io/rb/faster_path)
|
|
3
|
-
[](https://ci.appveyor.com/project/danielpclark/faster-path/branch/master)
|
|
3
|
+
[](https://github.com/danielpclark/faster_path/actions/workflows/ci.yml)
|
|
5
4
|
[](https://github.com/danielpclark/faster_path/tags)
|
|
6
5
|
[](https://github.com/danielpclark/faster_path/pulse)
|
|
7
|
-
[](https://github.com/danielpclark/faster_path/releases)
|
|
8
|
-
[](https://coveralls.io/github/danielpclark/faster_path?branch=master)
|
|
9
6
|
[](http://inch-ci.org/github/danielpclark/faster_path)
|
|
10
7
|
[](https://www.codetriage.com/danielpclark/faster_path)
|
|
11
8
|
|
|
@@ -103,7 +100,7 @@ So now we have some real numbers to work with We can be generoues and use 66% a
|
|
|
103
100
|
|
|
104
101
|
## What Rails Versions Will This Apply To?
|
|
105
102
|
|
|
106
|
-
As mentioned earlier Sprockets, which handles assets, changed away from using `Pathname` at all when moving from major version 2 to 3. So if you're using Sprockets 3 or later you won't reap the biggest performance
|
|
103
|
+
As mentioned earlier Sprockets, which handles assets, changed away from using `Pathname` at all when moving from major version 2 to 3. So if you're using Sprockets 3 or later you won't reap the biggest performance rewards from using this gem for now _(it's my goal to have this project become a core feature that Rails depends on and yes… that's a big ask)_. That is unless you write you're own implementation to re-integrate the use of `Pathname` and `FasterPath` into your asset handling library. For now just know that the Sprockets 2 series primarily works with Rails 4.1 and earlier. It may work in later Rails versions but I have not investigated this.
|
|
107
104
|
|
|
108
105
|
## Status
|
|
109
106
|
|
|
@@ -111,22 +108,29 @@ As mentioned earlier Sprockets, which handles assets, changed away from using `P
|
|
|
111
108
|
* Methods are stable
|
|
112
109
|
* Thoroughly tested
|
|
113
110
|
* Testers and developers are most welcome
|
|
114
|
-
* Windows
|
|
111
|
+
* Windows: paths follow Ruby's Windows rules (`\` and `/` separators, drive letters, UNC paths)
|
|
112
|
+
* Paths keep their encoding, as with Ruby's own methods
|
|
113
|
+
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
* Ruby 3.2, 3.3 or 3.4 (the Rust extension is built with [Rutie](https://github.com/danielpclark/rutie) 0.13, which supports these)
|
|
117
|
+
* Rust 1.71 or later: `gem install` builds the extension with Cargo (through the
|
|
118
|
+
[rutie](https://rubygems.org/gems/rutie) gem's Rake task) and keeps only the built library
|
|
115
119
|
|
|
116
120
|
## Installation
|
|
117
121
|
|
|
118
|
-
Ensure Rust is installed
|
|
122
|
+
Ensure Rust is installed, with `cargo` on your `PATH`:
|
|
119
123
|
|
|
120
|
-
[Rust
|
|
124
|
+
[Install Rust](https://www.rust-lang.org/tools/install)
|
|
121
125
|
|
|
122
126
|
```
|
|
123
|
-
curl -sSf https://
|
|
127
|
+
curl -sSf https://sh.rustup.rs | sh
|
|
124
128
|
```
|
|
125
129
|
|
|
126
130
|
Add this line to your application's Gemfile:
|
|
127
131
|
|
|
128
132
|
```ruby
|
|
129
|
-
gem 'faster_path', '~> 0.
|
|
133
|
+
gem 'faster_path', '~> 0.5.0'
|
|
130
134
|
```
|
|
131
135
|
|
|
132
136
|
And then execute:
|
|
@@ -137,6 +141,12 @@ Or install it yourself as:
|
|
|
137
141
|
|
|
138
142
|
$ gem install faster_path
|
|
139
143
|
|
|
144
|
+
Installing compiles the Rust extension for the Ruby doing the installing ("Building native
|
|
145
|
+
extensions. This could take a while..."), which takes about a minute the first time while Cargo
|
|
146
|
+
fetches and builds the dependencies. If Rust is missing, the install fails at that step with
|
|
147
|
+
Cargo not being found; install Rust and run it again. Nothing is downloaded from GitHub: there are
|
|
148
|
+
no prebuilt binaries.
|
|
149
|
+
|
|
140
150
|
## Visual Benchmarks
|
|
141
151
|
|
|
142
152
|
Benchmarks in Faster Path now produce visual graph charts of performance improvements.
|
|
@@ -147,27 +157,35 @@ improvement result for the `chop_basename` method.
|
|
|
147
157
|
|
|
148
158
|
## Usage
|
|
149
159
|
|
|
160
|
+
Add the proper require to your project.
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
require "faster_path"
|
|
164
|
+
```
|
|
165
|
+
|
|
150
166
|
Current methods implemented:
|
|
151
167
|
|
|
152
|
-
|FasterPath Rust Implementation|Ruby
|
|
153
|
-
|
|
154
|
-
| `FasterPath.absolute?` | `Pathname#absolute?` |
|
|
155
|
-
| `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` |
|
|
156
|
-
| `FasterPath.basename` | `File.basename` |
|
|
157
|
-
| `FasterPath.children` | `Pathname#children` |
|
|
158
|
-
| `FasterPath.chop_basename` | `Pathname#chop_basename` |
|
|
159
|
-
| `FasterPath.cleanpath_aggressive` | `Pathname#cleanpath_aggressive` |
|
|
160
|
-
| `FasterPath.cleanpath_conservative` | `Pathname#cleanpath_conservative` |
|
|
161
|
-
| `FasterPath.del_trailing_separator` | `Pathname#del_trailing_separator` |
|
|
162
|
-
| `FasterPath.directory?` | `Pathname#directory?` |
|
|
163
|
-
| `FasterPath.dirname` | `File.dirname` |
|
|
164
|
-
| `FasterPath.entries` | `Pathname#entries` |
|
|
165
|
-
| `FasterPath.extname` | `File.extname` |
|
|
166
|
-
| `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` |
|
|
167
|
-
| `FasterPath.
|
|
168
|
-
| `FasterPath.plus` | `Pathname#plus` | 94.7% |
|
|
169
|
-
| `FasterPath.relative?` | `Pathname#relative?` |
|
|
170
|
-
| `FasterPath.relative_path_from` | `Pathname#relative_path_from` |
|
|
168
|
+
|FasterPath Rust Implementation|Ruby Implementation|Time Shaved Off (Ruby 3.4)|Time Shaved Off (Ruby 3.3)|Time Shaved Off (Ruby 3.2)|
|
|
169
|
+
|---|---|:---:|:---:|:---:|
|
|
170
|
+
| `FasterPath.absolute?` | `Pathname#absolute?` | 55.5% | 53.2% | 43.1% |
|
|
171
|
+
| `FasterPath.add_trailing_separator` | `Pathname#add_trailing_separator` | 87.4% | 85.3% | 85.8% |
|
|
172
|
+
| `FasterPath.basename` | `File.basename` | 30.7% | 22.2% | 27.5% |
|
|
173
|
+
| `FasterPath.children` | `Pathname#children` | 65.0% | 55.1% | 55.4% |
|
|
174
|
+
| `FasterPath.chop_basename` | `Pathname#chop_basename` | 75.3% | 73.5% | 72.8% |
|
|
175
|
+
| `FasterPath.cleanpath_aggressive` | `Pathname#cleanpath_aggressive` | 92.4% | 92.2% | 92.4% |
|
|
176
|
+
| `FasterPath.cleanpath_conservative` | `Pathname#cleanpath_conservative` | 91.4% | 91.3% | 91.8% |
|
|
177
|
+
| `FasterPath.del_trailing_separator` | `Pathname#del_trailing_separator` | 89.1% | 87.9% | 87.8% |
|
|
178
|
+
| `FasterPath.directory?` | `Pathname#directory?` | 41.5% | 39.9% | 44.2% |
|
|
179
|
+
| `FasterPath.dirname` | `File.dirname` | 52.0% | 40.3% | 40.8% |
|
|
180
|
+
| `FasterPath.entries` | `Pathname#entries` | 57.8% | 42.4% | 45.9% |
|
|
181
|
+
| `FasterPath.extname` | `File.extname` | 69.4% | 66.7% | 69.2% |
|
|
182
|
+
| `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` | 93.4% | 92.8% | 91.7% |
|
|
183
|
+
| `FasterPath.join` | `Pathname#join` | 89.1% | 89.0% | 88.4% |
|
|
184
|
+
| `FasterPath.plus` | `Pathname#plus` | 95.5% | 94.7% | 94.5% |
|
|
185
|
+
| `FasterPath.relative?` | `Pathname#relative?` | 66.9% | 66.3% | 60.4% |
|
|
186
|
+
| `FasterPath.relative_path_from` | `Pathname#relative_path_from` | 94.4% | 94.6% | 94.2% |
|
|
187
|
+
|
|
188
|
+
See [Benchmarks](#benchmarks) for how these are measured.
|
|
171
189
|
|
|
172
190
|
You may choose to use the methods directly, or scope change to rewrite behavior on the
|
|
173
191
|
standard library with the included refinements, or even call a method to monkeypatch
|
|
@@ -193,14 +211,74 @@ FasterPath.sledgehammer_everything!
|
|
|
193
211
|
|
|
194
212
|
These will **not** be included by default in monkey-patches. To try them with monkeypatching use the environment flag of `WITH_REGRESSION`. These methods are here to be improved upon.
|
|
195
213
|
|
|
196
|
-
|FasterPath Implementation|Ruby Implementation|
|
|
197
|
-
|
|
198
|
-
| `FasterPath.entries_compat` | `Pathname.entries` |
|
|
199
|
-
| `FasterPath.children_compat` | `Pathname.children` |
|
|
214
|
+
|FasterPath Implementation|Ruby Implementation|Time Shaved Off (Ruby 3.4)|Time Shaved Off (Ruby 3.3)|Time Shaved Off (Ruby 3.2)|
|
|
215
|
+
|---|---|:---:|:---:|:---:|
|
|
216
|
+
| `FasterPath.entries_compat` | `Pathname.entries` | 35.7% | 22.0% | 15.9% |
|
|
217
|
+
| `FasterPath.children_compat` | `Pathname.children` | 46.7% | 33.6% | 32.3% |
|
|
200
218
|
|
|
201
219
|
It's been my observation (and some others) that the Rust implementation of the C code for `File` has similar results but
|
|
202
220
|
performance seems to vary based on CPU cache on possibly 64bit/32bit system environments. These are not included by default when the monkey patch method `FasterPath.sledgehammer_everything!` is executed.
|
|
203
221
|
|
|
222
|
+
## Benchmarks
|
|
223
|
+
|
|
224
|
+
The "Time Shaved Off" figures come from the project's Pinch-bench (`rake pbench`, in
|
|
225
|
+
`test/pbench`): each method runs against Ruby's own implementation, alternating with it round by
|
|
226
|
+
round, and the result is how much less time FasterPath's best round took than Ruby's best round.
|
|
227
|
+
They are the median of 3 runs of `LONG_RUN=30 rake pbench` (5 rounds of 300,000 calls of each
|
|
228
|
+
method) on Linux x86_64, each run pinned to one CPU core, with Ruby 3.4.6, 3.3.6 and 3.2.9, built
|
|
229
|
+
with Rust 1.97. The three runs agree within a few points for most methods; the ones that touch
|
|
230
|
+
the filesystem (`children`, `directory?`, `entries`) vary the most.
|
|
231
|
+
|
|
232
|
+
The figures are smaller than they were on Ruby 2 for the cheapest methods, `absolute?` and
|
|
233
|
+
`relative?` above all: Ruby 3's own `Pathname#absolute?` is a single regexp match, where Ruby 2's
|
|
234
|
+
walked the path with `chop_basename`, so there is less left to shave off.
|
|
235
|
+
|
|
236
|
+
`rake pbench` is also the performance regression test: each method has a floor in
|
|
237
|
+
`test/pbench/pbench_suite.rb` (about half of its lowest figure in the table above) and the task
|
|
238
|
+
fails when a method measures below it, so a change that makes a method slower shows up in
|
|
239
|
+
`rake test`. On Windows, where `join` is currently slower than Ruby's
|
|
240
|
+
([#185](https://github.com/danielpclark/faster_path/issues/185)), the floors are reported but not
|
|
241
|
+
enforced.
|
|
242
|
+
|
|
243
|
+
### Before and after the Rutie 0.10 upgrade
|
|
244
|
+
|
|
245
|
+
The last figures on Ruby 2, from the Rutie 0.10 upgrade (faster_path 0.4.0): Ruby 2.7.8 and
|
|
246
|
+
2.5.7, alternating runs of the code before the upgrade (built with Rutie 0.6) and after it, with
|
|
247
|
+
the benchmark then measuring the average of 5 rounds run one implementation after the other.
|
|
248
|
+
Higher is better; the change is in percentage points.
|
|
249
|
+
|
|
250
|
+
|Method|Ruby 2.7 before|Ruby 2.7 after|Change|Ruby 2.5 before|Ruby 2.5 after|Change|
|
|
251
|
+
|---|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
252
|
+
| `absolute?` | 93.3% | 95.7% | +2.4 | 96.1% | 96.7% | +0.6 |
|
|
253
|
+
| `add_trailing_separator` | 66.6% | 85.4% | **+18.8** | 64.3% | 83.0% | **+18.7** |
|
|
254
|
+
| `basename` | 42.4% | 42.4% | 0.0 | 32.1% | 39.1% | **+7.0** |
|
|
255
|
+
| `children` | 52.8% | 54.7% | +1.9 | 46.5% | 47.8% | +1.3 |
|
|
256
|
+
| `children_compat` | −1.2% | 32.1% | **+33.3** | 1.1% | 26.6% | **+25.5** |
|
|
257
|
+
| `chop_basename` | 71.2% | 70.4% | −0.8 | 74.7% | 75.1% | +0.4 |
|
|
258
|
+
| `cleanpath_aggressive` | 90.3% | 90.3% | 0.0 | 92.7% | 90.6% | −2.1 |
|
|
259
|
+
| `cleanpath_conservative` | 89.8% | 89.6% | −0.2 | 91.7% | 91.6% | −0.1 |
|
|
260
|
+
| `del_trailing_separator` | 86.5% | 85.3% | −1.2 | 87.0% | 87.6% | +0.6 |
|
|
261
|
+
| `directory?` | 33.2% | 34.3% | +1.1 | 37.2% | 37.5% | +0.3 |
|
|
262
|
+
| `dirname` | 49.8% | 56.8% | **+7.0** | 51.2% | 54.7% | +3.5 |
|
|
263
|
+
| `entries` | 40.4% | 38.0% | −2.4 | 33.6% | 33.9% | +0.3 |
|
|
264
|
+
| `entries_compat` | −29.8% | 16.8% | **+46.6** | −23.3% | 9.2% | **+32.5** |
|
|
265
|
+
| `extname` | 70.3% | 74.0% | +3.7 | 74.0% | 73.1% | −0.9 |
|
|
266
|
+
| `has_trailing_separator?` | 87.5% | 88.5% | +1.0 | 87.1% | 89.1% | +2.0 |
|
|
267
|
+
| `join` | crashes | 88.1% | | crashes | 90.3% | |
|
|
268
|
+
| `plus` | 92.3% | 91.7% | −0.6 | 93.0% | 93.4% | +0.4 |
|
|
269
|
+
| `relative?` | 90.7% | 92.8% | +2.1 | 93.6% | 95.2% | +1.6 |
|
|
270
|
+
| `relative_path_from` | 92.7% | 92.5% | −0.2 | 94.0% | 93.6% | −0.4 |
|
|
271
|
+
|
|
272
|
+
Before the upgrade, `FasterPath.join` segfaulted, so its benchmark can't be measured.
|
|
273
|
+
`add_trailing_separator`, `dirname` and the `_compat` methods gained the most:
|
|
274
|
+
`add_trailing_separator` no longer builds a temporary string, no method checks its arguments
|
|
275
|
+
for valid UTF-8 any more, and the `_compat` methods build `Pathname` objects without calling
|
|
276
|
+
`Pathname.new`. `basename` was re-measured after it was made to take a shorter path on Unix
|
|
277
|
+
and skip an empty extension; it was the one method that first measured slower on Ruby 2.5.
|
|
278
|
+
Its timings vary a lot from run to run (Ruby 2.5 before: 40.0%, 32.1%, 28.7%; after: 29.6%,
|
|
279
|
+
39.1%, 39.2%), so its instruction counts from `valgrind --tool=callgrind` are a steadier
|
|
280
|
+
measure. They went from 2439 to 2331 per call on Ruby 2.5 and from 2689 to 2561 on Ruby 2.7.
|
|
281
|
+
|
|
204
282
|
## Getting Started with Development
|
|
205
283
|
|
|
206
284
|
The primary methods to target are mostly listed in the **Why** section above. You may find the Ruby
|
|
@@ -214,7 +292,9 @@ Rust method like `!absolute?` _(not absolute)_ drops 39% of the performance alre
|
|
|
214
292
|
Whenever feasible implement it in Rust.
|
|
215
293
|
|
|
216
294
|
After checking out the repo, make sure you have Rust installed, then run `bundle`.
|
|
217
|
-
Run `rake test` to run the tests, and `rake bench` for benchmarks.
|
|
295
|
+
Run `rake test` to run the tests, and `rake bench` for benchmarks. Both build the extension
|
|
296
|
+
first (`rake rutie:build`, into `target/release`, which is where `require 'faster_path'` loads it
|
|
297
|
+
from in a checkout).
|
|
218
298
|
|
|
219
299
|
### Building and running tests
|
|
220
300
|
|
data/Rakefile
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
2
|
require 'rake/testtask'
|
|
3
|
-
require '
|
|
4
|
-
require 'faster_path/
|
|
3
|
+
require 'faster_path/version'
|
|
4
|
+
require 'faster_path/build_task'
|
|
5
5
|
|
|
6
6
|
desc 'System Details'
|
|
7
7
|
task :sysinfo do
|
|
@@ -22,7 +22,7 @@ task :sysinfo do
|
|
|
22
22
|
puts "RAKE\n #{Rake::VERSION}"
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
FasterPath::BuildTask.define
|
|
26
26
|
|
|
27
27
|
desc "Generate Contriburs.md Manifest"
|
|
28
28
|
task :contrib do
|
|
@@ -31,10 +31,8 @@ task :contrib do
|
|
|
31
31
|
' > Contributors.md;git shortlog -s -n -e | sed 's/^......./- /' >> Contributors.md"
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
desc 'Build
|
|
35
|
-
task build_lib: '
|
|
36
|
-
thermite.run_cargo 'clean'
|
|
37
|
-
end
|
|
34
|
+
desc 'Build the Rust extension'
|
|
35
|
+
task build_lib: 'rutie:build'
|
|
38
36
|
|
|
39
37
|
desc 'Code Quality Check'
|
|
40
38
|
task :lint do
|
|
@@ -46,7 +44,14 @@ end
|
|
|
46
44
|
|
|
47
45
|
desc "Run Rust Tests"
|
|
48
46
|
task :cargo do
|
|
49
|
-
|
|
47
|
+
ruby_lib = RbConfig::CONFIG['libdir']
|
|
48
|
+
sh(
|
|
49
|
+
{
|
|
50
|
+
'LD_LIBRARY_PATH' => ruby_lib,
|
|
51
|
+
'DYLD_LIBRARY_PATH' => ruby_lib
|
|
52
|
+
},
|
|
53
|
+
'cargo -vv test -- --nocapture'
|
|
54
|
+
)
|
|
50
55
|
end
|
|
51
56
|
|
|
52
57
|
Rake::TestTask.new(minitest: :build_lib) do |t|
|
|
@@ -62,7 +67,9 @@ task :init_mspec do |_t|
|
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
task test: [:cargo, :minitest, :lint, :pbench, :init_mspec] do |_t|
|
|
65
|
-
|
|
70
|
+
# Run through Ruby so this also works on Windows
|
|
71
|
+
exec Gem.ruby, 'spec/mspec/bin/mspec', '--format', 'spec',
|
|
72
|
+
'core/file/basename', 'core/file/extname', 'core/file/dirname', 'library/pathname'
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
desc "Full mspec results w/o encoding"
|
|
@@ -83,10 +90,8 @@ Rake::TestTask.new(bench: :build_lib) do |t|
|
|
|
83
90
|
t.pattern = 'test/**/*_benchmark.rb'
|
|
84
91
|
end
|
|
85
92
|
|
|
93
|
+
# LONG_RUN=<1..30> multiplies the calls per round, see test/pbench/pbench.rb
|
|
86
94
|
Rake::TestTask.new(pbench: :build_lib) do |t|
|
|
87
|
-
if ARGV.last == '--long-run'
|
|
88
|
-
ENV['LONG_RUN'] = '10'
|
|
89
|
-
end
|
|
90
95
|
t.libs = %w[lib test test/pbench]
|
|
91
96
|
t.pattern = 'test/pbench/pbench_suite.rb'
|
|
92
97
|
end
|
data/ext/Rakefile
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require_relative '../lib/faster_path/
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require_relative '../lib/faster_path/build_task'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
Thermite::Tasks.new(cargo_project_path: project_toplevel_dir,
|
|
6
|
-
ruby_project_path: project_toplevel_dir)
|
|
4
|
+
FasterPath::BuildTask.define
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
# Keeps the built library (in lib/faster_path, where FasterPath loads it from)
|
|
7
|
+
# and removes the rest of Cargo's output from the installed gem.
|
|
8
|
+
task install_library: 'rutie:build' do
|
|
9
|
+
FileUtils.mkdir_p(FasterPath::BuildTask::INSTALLED_LIBRARY_DIR)
|
|
10
|
+
FileUtils.cp(FasterPath::BuildTask.built_library, FasterPath::BuildTask::INSTALLED_LIBRARY_DIR)
|
|
11
|
+
Rake::Task['rutie:clean'].invoke
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
task default: :install_library
|
data/faster_path.gemspec
CHANGED
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
spec.files = [
|
|
17
17
|
'Cargo.lock', 'Cargo.toml', 'Gemfile', 'MIT-LICENSE.txt', 'README.md', 'Rakefile',
|
|
18
18
|
'bin/console', 'bin/setup', 'ext/Rakefile', 'faster_path.gemspec', 'lib/faster_path.rb',
|
|
19
|
-
'lib/faster_path/version.rb', 'lib/faster_path/
|
|
19
|
+
'lib/faster_path/version.rb', 'lib/faster_path/build_task.rb',
|
|
20
20
|
'lib/faster_path/optional/monkeypatches.rb', 'lib/faster_path/optional/refinements.rb'
|
|
21
21
|
]
|
|
22
22
|
spec.files += Dir['src/**/*']
|
|
@@ -24,14 +24,18 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.extensions = ['ext/Rakefile']
|
|
25
25
|
spec.require_paths = ['lib']
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
spec.
|
|
29
|
-
|
|
27
|
+
# Rutie 0.13, which the Rust extension is built with, supports Ruby 3.2 to 3.4
|
|
28
|
+
spec.required_ruby_version = ['>= 3.2', '< 3.5']
|
|
29
|
+
|
|
30
|
+
# The extension is built at install time with Rutie's Rake task and loaded with Rutie
|
|
31
|
+
spec.add_dependency 'rake', '>= 12.3'
|
|
32
|
+
spec.add_dependency 'rutie', '~> 0.0.5'
|
|
30
33
|
spec.add_development_dependency 'read_source', '~> 0.2.6'
|
|
31
|
-
spec.add_development_dependency 'minitest', '~> 5.
|
|
34
|
+
spec.add_development_dependency 'minitest', '~> 5.11'
|
|
32
35
|
spec.add_development_dependency 'minitest-reporters', '~> 1.1'
|
|
33
36
|
spec.add_development_dependency 'color_pound_spec_reporter', '~> 0.0.9'
|
|
34
|
-
spec.add_development_dependency 'rubocop', '
|
|
37
|
+
spec.add_development_dependency 'rubocop', '~> 1.80'
|
|
38
|
+
spec.add_development_dependency 'rubocop-performance', '~> 1.20'
|
|
35
39
|
spec.add_development_dependency 'stop_watch', '~> 1.0'
|
|
36
40
|
if !ENV['CI'] && ENV['GRAPH']
|
|
37
41
|
spec.add_development_dependency 'gruff', '~> 0.7.0'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'rbconfig'
|
|
2
|
+
require 'rutie'
|
|
3
|
+
require 'rutie/rake_task'
|
|
4
|
+
|
|
5
|
+
module FasterPath
|
|
6
|
+
# Defines `rutie:build` and `rutie:clean`, which build the Rust extension
|
|
7
|
+
# with Cargo. Used by the Rakefile and by ext/Rakefile when the gem is
|
|
8
|
+
# installed; both may run from another directory, so Cargo is pointed at
|
|
9
|
+
# the gem's Cargo.toml.
|
|
10
|
+
#
|
|
11
|
+
# The Rakefile leaves the library in target/release, where Rutie loads it
|
|
12
|
+
# from; ext/Rakefile moves it to lib/faster_path so that the installed gem
|
|
13
|
+
# keeps nothing else of the build.
|
|
14
|
+
module BuildTask
|
|
15
|
+
PROJECT_DIR = File.expand_path('../..', __dir__)
|
|
16
|
+
# Where `gem install` puts the built library
|
|
17
|
+
INSTALLED_LIBRARY_DIR = File.join(PROJECT_DIR, 'lib', 'faster_path')
|
|
18
|
+
|
|
19
|
+
def self.define
|
|
20
|
+
# Rutie's build script links the extension to the Ruby named by RUBY,
|
|
21
|
+
# or the first `ruby` on the PATH; make it the Ruby running Rake.
|
|
22
|
+
ENV['RUBY'] ||= File.join(RbConfig::CONFIG['bindir'],
|
|
23
|
+
RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT'])
|
|
24
|
+
Rutie::RakeTask.new(cargo_args: ['--manifest-path', File.join(PROJECT_DIR, 'Cargo.toml')])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# The library `rutie:build` builds, in the project's target/release
|
|
28
|
+
def self.built_library
|
|
29
|
+
Rutie.new(:faster_path).ffi_library(File.join(PROJECT_DIR, 'lib'))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -24,10 +24,10 @@ module FasterPath
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# @private :nodoc:
|
|
27
|
-
def self.dirname(pth)
|
|
27
|
+
def self.dirname(pth, level = 1)
|
|
28
28
|
pth = pth.to_path if pth.respond_to? :to_path
|
|
29
29
|
raise TypeError unless pth.is_a? String
|
|
30
|
-
FasterPath.dirname(pth)
|
|
30
|
+
FasterPath.dirname(pth, level)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -20,10 +20,10 @@ module FasterPath
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# @private :nodoc:
|
|
23
|
-
def self.dirname(pth)
|
|
23
|
+
def self.dirname(pth, level = 1)
|
|
24
24
|
pth = pth.to_path if pth.respond_to? :to_path
|
|
25
25
|
raise TypeError unless pth.is_a? String
|
|
26
|
-
FasterPath.dirname(pth)
|
|
26
|
+
FasterPath.dirname(pth, level)
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
data/lib/faster_path/version.rb
CHANGED
data/lib/faster_path.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require 'faster_path/version'
|
|
2
2
|
require 'pathname'
|
|
3
|
-
require '
|
|
4
|
-
require 'faster_path/thermite_initialize'
|
|
3
|
+
require 'rutie'
|
|
5
4
|
require 'fiddle'
|
|
6
5
|
require 'fiddle/import'
|
|
7
6
|
|
|
@@ -14,16 +13,12 @@ require 'fiddle/import'
|
|
|
14
13
|
# existing code ecosystem. To do so you will need to
|
|
15
14
|
# `require 'faster_path/optional/monkeypatches'` beforehand.
|
|
16
15
|
module FasterPath
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Fiddle::Function.
|
|
25
|
-
new(Fiddle.dlopen(FFI_LIBRARY)['Init_faster_pathname'], [], Fiddle::TYPE_VOIDP).
|
|
26
|
-
call
|
|
16
|
+
# The Rust extension: in the project's target/release after `rake rutie:build`,
|
|
17
|
+
# or in lib/faster_path, where `gem install` puts it (see ext/Rakefile)
|
|
18
|
+
RUTIE = [Rutie.new(:faster_path), Rutie.new(:faster_path, lib_path: 'faster_path')].
|
|
19
|
+
find { |rutie| File.exist?(rutie.ffi_library(__dir__)) } || Rutie.new(:faster_path)
|
|
20
|
+
FFI_LIBRARY = RUTIE.ffi_library(__dir__)
|
|
21
|
+
RUTIE.init('Init_faster_pathname', __dir__)
|
|
27
22
|
|
|
28
23
|
Public.class_eval do
|
|
29
24
|
private_class_method :basename
|
|
@@ -40,7 +35,9 @@ module FasterPath
|
|
|
40
35
|
# The Ruby architecture bit width: 64bits or 32bits
|
|
41
36
|
# @return [Integer]
|
|
42
37
|
def self.ruby_arch_bits
|
|
43
|
-
1.size
|
|
38
|
+
# The width of a pointer; `1.size` is the width of a C `long`, which is
|
|
39
|
+
# 4 bytes on 64-bit Windows.
|
|
40
|
+
[0].pack('J').bytesize * 8
|
|
44
41
|
end
|
|
45
42
|
|
|
46
43
|
# A very fast way to check if a string is blank
|