sin_fast_blank 4.0.1 → 5.0.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/CHANGELOG.md +215 -0
- data/LICENSE.txt +23 -0
- data/README.md +307 -0
- data/Rakefile +45 -0
- data/ext/sin_fast_blank/extconf.rb +30 -5
- data/ext/sin_fast_blank/sin_fast_blank.c +325 -174
- data/lib/sin_fast_blank/version.rb +5 -0
- data/lib/sin_fast_blank.rb +19 -0
- metadata +14 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85588dad4cdf2b592985b87f2119e5ace5a062e9a36bd2cf2ea760b499781112
|
|
4
|
+
data.tar.gz: bcc255feaac9280e816034458ffe39cad5fc954df31ef247e4d6da6cbe85c487
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7798965cc3270c0b9b61f1e0d5b2afd00b18d8ba4e7447d444343f8c42f72ac05bbd0b7a03d3b5b7fc03d64e20380ac9c8b2df18c07a9793649ff763b2f51e5c
|
|
7
|
+
data.tar.gz: 21b941b1f86e7e3ddfcca4b213f0430083eeb8c99962a036b837e0127608775785e4532d5544269ff6c1e1d89bb8080c27b014571cb288c8e300a2c24642b192
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [5.0.0] - 2026-08-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add Ractor support
|
|
13
|
+
- Add SSE2 SIMD support on mswin builds
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Improve SIMD performance with overlapped final chunks and continued ASCII scanning
|
|
18
|
+
- Skip the dispatch pointer for short strings
|
|
19
|
+
- Simplify native extension loading
|
|
20
|
+
- Refactor Java extension
|
|
21
|
+
- Add JRuby build and test script
|
|
22
|
+
- Build JRuby extension against the oldest supported JRuby
|
|
23
|
+
- Probe compiler flags before adding them
|
|
24
|
+
- Drop the unused 32-bit ARM NEON flag
|
|
25
|
+
- Make test task depend on compile
|
|
26
|
+
- Minimize gem version constraints
|
|
27
|
+
- Remove unneeded json gem dependency
|
|
28
|
+
- Update tests
|
|
29
|
+
- Improve test CI
|
|
30
|
+
- Improve lint CI
|
|
31
|
+
- Require tests to pass before releasing
|
|
32
|
+
- Upgrade actions/checkout to v7
|
|
33
|
+
- Improve benchmark
|
|
34
|
+
- Update .rubocop.yml
|
|
35
|
+
- Update .gitignore
|
|
36
|
+
- Update README.md
|
|
37
|
+
- Update sponsor links
|
|
38
|
+
- Update compiled jar
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Fix `blank?` to match per-encoding space tables
|
|
43
|
+
- Fix `blank?` to raise only when Ruby calls the string broken
|
|
44
|
+
- Fix `ascii_blank?` to answer instead of raising on invalid byte sequences
|
|
45
|
+
- Fix AVX2 selection to use runtime CPU detection
|
|
46
|
+
- Fix Java extension loading without `JRuby::Util.load_ext`
|
|
47
|
+
- Fix packaged gem to include files under lib
|
|
48
|
+
- Fix C extension install path
|
|
49
|
+
- Fix stray compiled jar getting into the packaged gem
|
|
50
|
+
- Fix benchmark
|
|
51
|
+
|
|
52
|
+
## [4.0.1] - 2026-02-22
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- Improve SIMD performance with range-based approach and add `ascii_blank?` SIMD support
|
|
57
|
+
- Refactor Java extension
|
|
58
|
+
- Format C and Java code
|
|
59
|
+
- Add C and Java lint CI jobs
|
|
60
|
+
- Upgrade actions/checkout to v6
|
|
61
|
+
- Pin gem versions
|
|
62
|
+
- Add .clang-format configuration
|
|
63
|
+
- Improve benchmark
|
|
64
|
+
- Update README.md
|
|
65
|
+
- Update .gitignore
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- Fix Java extension compatibility checks
|
|
70
|
+
- Fix C extension ARM NEON and pointer safety issues
|
|
71
|
+
- Fix benchmark
|
|
72
|
+
- Fix clang-format lint path
|
|
73
|
+
|
|
74
|
+
## [4.0.0] - 2025-08-13
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- Improve performance
|
|
79
|
+
- Update tests
|
|
80
|
+
- Update benchmark
|
|
81
|
+
- Update .rubocop.yml
|
|
82
|
+
- Improve test CI
|
|
83
|
+
- Improve lint CI
|
|
84
|
+
- Update .gitignore
|
|
85
|
+
- Update README.md
|
|
86
|
+
|
|
87
|
+
## [3.1.1] - 2025-03-18
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- Fix test CI
|
|
92
|
+
|
|
93
|
+
### Changed
|
|
94
|
+
|
|
95
|
+
- Update summary
|
|
96
|
+
|
|
97
|
+
## [3.1.0] - 2025-03-12
|
|
98
|
+
|
|
99
|
+
### Changed
|
|
100
|
+
|
|
101
|
+
- Refactor implementation
|
|
102
|
+
- Update summary
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- Fix test
|
|
107
|
+
|
|
108
|
+
## [3.0.0] - 2025-03-05
|
|
109
|
+
|
|
110
|
+
### Changed
|
|
111
|
+
|
|
112
|
+
- Rename `String#blank_as?` to `String#blank?`
|
|
113
|
+
- Rename `String#blank?` to `String#ascii_blank?`
|
|
114
|
+
- Update tests
|
|
115
|
+
- Update benchmark
|
|
116
|
+
- Update README.md
|
|
117
|
+
|
|
118
|
+
## [2.2.1] - 2025-03-03
|
|
119
|
+
|
|
120
|
+
### Changed
|
|
121
|
+
|
|
122
|
+
- Update tests
|
|
123
|
+
- Update summary
|
|
124
|
+
|
|
125
|
+
### Fixed
|
|
126
|
+
|
|
127
|
+
- Fix benchmark
|
|
128
|
+
|
|
129
|
+
## [2.2.0] - 2025-03-02
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
|
|
133
|
+
- Add .editorconfig
|
|
134
|
+
|
|
135
|
+
### Changed
|
|
136
|
+
|
|
137
|
+
- Improve performance
|
|
138
|
+
- Update benchmark
|
|
139
|
+
- Update .rubocop.yml
|
|
140
|
+
- Update summary
|
|
141
|
+
- Update README.md
|
|
142
|
+
|
|
143
|
+
## [2.1.0] - 2025-01-17
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
|
|
147
|
+
- Improve gemspec
|
|
148
|
+
- Improve test CI
|
|
149
|
+
|
|
150
|
+
## [2.0.0] - 2025-01-06
|
|
151
|
+
|
|
152
|
+
### Added
|
|
153
|
+
|
|
154
|
+
- Add bin
|
|
155
|
+
|
|
156
|
+
### Changed
|
|
157
|
+
|
|
158
|
+
- Improve implementation
|
|
159
|
+
- Improve tests
|
|
160
|
+
- Update .gitignore
|
|
161
|
+
- Update Gemfile
|
|
162
|
+
- Update gemspec
|
|
163
|
+
- Improve CI
|
|
164
|
+
- Improve documents
|
|
165
|
+
|
|
166
|
+
### Removed
|
|
167
|
+
|
|
168
|
+
- Drop runtime support for Ruby 2.2 and below
|
|
169
|
+
|
|
170
|
+
## [1.0.1] - 2021-08-16/2021-12-06
|
|
171
|
+
|
|
172
|
+
### Added
|
|
173
|
+
|
|
174
|
+
- Support JRuby
|
|
175
|
+
|
|
176
|
+
### Changed
|
|
177
|
+
|
|
178
|
+
- Avoid warnings if redefining blank?
|
|
179
|
+
|
|
180
|
+
## [1.0.0] - 2015-08-03
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- Ruby 2.2 support ([@tjschuck](https://github.com/tjschuck) — [#9](https://github.com/SamSaffron/fast_blank/pull/9))
|
|
185
|
+
|
|
186
|
+
## [0.0.2] - 2013-11-20
|
|
187
|
+
|
|
188
|
+
### Changed
|
|
189
|
+
|
|
190
|
+
- Unrolled internal loop to improve perf ([@tmm1](https://github.com/tmm1) — [#2](https://github.com/SamSaffron/fast_blank/pull/2))
|
|
191
|
+
|
|
192
|
+
### Removed
|
|
193
|
+
|
|
194
|
+
- Removed rake dependency ([@tmm1](https://github.com/tmm1) — [#2](https://github.com/SamSaffron/fast_blank/pull/2))
|
|
195
|
+
|
|
196
|
+
## [0.0.1] - 2013-04-07
|
|
197
|
+
|
|
198
|
+
### Added
|
|
199
|
+
|
|
200
|
+
- Initial release
|
|
201
|
+
|
|
202
|
+
[5.0.0]: https://github.com/cadenza-tech/sin_fast_blank/compare/v4.0.1...v5.0.0
|
|
203
|
+
[4.0.1]: https://github.com/cadenza-tech/sin_fast_blank/compare/v4.0.0...v4.0.1
|
|
204
|
+
[4.0.0]: https://github.com/cadenza-tech/sin_fast_blank/compare/v3.1.1...v4.0.0
|
|
205
|
+
[3.1.1]: https://github.com/cadenza-tech/sin_fast_blank/compare/v3.1.0...v3.1.1
|
|
206
|
+
[3.1.0]: https://github.com/cadenza-tech/sin_fast_blank/compare/v3.0.0...v3.1.0
|
|
207
|
+
[3.0.0]: https://github.com/cadenza-tech/sin_fast_blank/compare/v2.2.1...v3.0.0
|
|
208
|
+
[2.2.1]: https://github.com/cadenza-tech/sin_fast_blank/compare/v2.2.0...v2.2.1
|
|
209
|
+
[2.2.0]: https://github.com/cadenza-tech/sin_fast_blank/compare/v2.1.0...v2.2.0
|
|
210
|
+
[2.1.0]: https://github.com/cadenza-tech/sin_fast_blank/compare/v2.0.0...v2.1.0
|
|
211
|
+
[2.0.0]: https://github.com/cadenza-tech/sin_fast_blank/compare/v1.0.1...v2.0.0
|
|
212
|
+
[1.0.1]: https://github.com/cadenza-tech/sin_fast_blank/compare/1.0.0...v1.0.1
|
|
213
|
+
[1.0.0]: https://github.com/cadenza-tech/sin_fast_blank/compare/0.0.2...1.0.0
|
|
214
|
+
[0.0.2]: https://github.com/cadenza-tech/sin_fast_blank/compare/0.0.1...0.0.2
|
|
215
|
+
[0.0.1]: https://github.com/cadenza-tech/sin_fast_blank/releases/tag/0.0.1
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2006 Steve Sloan
|
|
4
|
+
Copyright (c) 2013 Sam Saffron
|
|
5
|
+
Copyright (c) 2025 Masahiro
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in
|
|
15
|
+
all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# SinFastBlank
|
|
2
|
+
|
|
3
|
+
[](https://github.com/cadenza-tech/sin_fast_blank/blob/main/LICENSE.txt) [](https://github.com/cadenza-tech/sin_fast_blank/blob/main/CHANGELOG.md) [](https://github.com/cadenza-tech/sin_fast_blank/actions?query=workflow%3Arelease) [](https://github.com/cadenza-tech/sin_fast_blank/actions?query=workflow%3Atest) [](https://github.com/cadenza-tech/sin_fast_blank/actions?query=workflow%3Alint)
|
|
4
|
+
|
|
5
|
+
Check for blank string faster than FastBlank or ActiveSupport.
|
|
6
|
+
|
|
7
|
+
Forked from [FastBlank](https://github.com/SamSaffron/fast_blank).
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Usage](#usage)
|
|
11
|
+
- [String#blank?](#stringblank)
|
|
12
|
+
- [String#ascii\_blank?](#stringascii_blank)
|
|
13
|
+
- [Benchmark](#benchmark)
|
|
14
|
+
- [Development](#development)
|
|
15
|
+
- [Building for JRuby](#building-for-jruby)
|
|
16
|
+
- [Changelog](#changelog)
|
|
17
|
+
- [Contributing](#contributing)
|
|
18
|
+
- [License](#license)
|
|
19
|
+
- [Code of Conduct](#code-of-conduct)
|
|
20
|
+
- [Sponsor](#sponsor)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bundle add sin_fast_blank
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
gem install sin_fast_blank
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
Both methods can be called inside non-main Ractors on CRuby 3.0+.
|
|
39
|
+
|
|
40
|
+
ActiveSupport defines `String#blank?` on String itself in every version, so whichever of the two is loaded last replaces the other: require `sin_fast_blank` after ActiveSupport. `require 'active_support'` on its own does not reach that definition, while `active_support/all`, `active_support/core_ext/object`, `active_support/core_ext/object/blank` and `rails/all` do. `String.instance_method(:blank?).source_location` is `nil` while the extension is the one in place. `String#ascii_blank?` has no ActiveSupport counterpart and is never replaced.
|
|
41
|
+
|
|
42
|
+
### String#blank?
|
|
43
|
+
|
|
44
|
+
SinFastBlank's String#blank? is compatible with ActiveSupport's String#blank?.
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
require 'sin_fast_blank'
|
|
48
|
+
|
|
49
|
+
''.blank? # => true
|
|
50
|
+
' '.blank? # => true
|
|
51
|
+
' '.blank? # => true
|
|
52
|
+
"\t".blank? # => true
|
|
53
|
+
"\r".blank? # => true
|
|
54
|
+
"\n".blank? # => true
|
|
55
|
+
"\r\n".blank? # => true
|
|
56
|
+
"\r\n\v\f\r\s\t".blank? # => true
|
|
57
|
+
'abc'.blank? # => false
|
|
58
|
+
' abc '.blank? # => false
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The check is encoding-aware: codepoints match the same per-encoding `[[:space:]]` tables that ActiveSupport's regexp uses. This holds on CRuby and JRuby. TruffleRuby's regexp engine consults a different table, so a handful of single-byte cases disagree: it reports `0x85` and `0xA0` as blank in encodings whose ctype table does not, such as `ASCII-8BIT`.
|
|
62
|
+
|
|
63
|
+
Dummy encodings are the one place the two part ways. ActiveSupport cannot build its regexp for a string tagged `ISO-2022-JP`, `UTF-16` or `UTF-7`, so it raises `RegexpError` or `Encoding::ConverterNotFoundError`; `String#blank?` scans the bytes and answers.
|
|
64
|
+
|
|
65
|
+
SinFastBlank scans left to right and returns as soon as the result is decided, so it can answer before reaching an invalid byte sequence that ActiveSupport's whole-string regexp would trip over: `"a\xFF".blank?` returns `false` where ActiveSupport raises `ArgumentError`. Once the scan does reach one, `blank?` matches ActiveSupport exactly: it raises `ArgumentError` for a string Ruby itself calls broken, and answers `false` for one whose bytes Ruby's own transcoder produced but its scanner rejects (`'À'.encode('Big5-HKSCS')`), which ActiveSupport does not treat as an error either.
|
|
66
|
+
|
|
67
|
+
One more case parts ways, this time on the receiver rather than its bytes: ActiveSupport shortcuts on `empty?` before matching its regexp, so a String that overrides `empty?` to return true is blank there, where `String#blank?` reads the bytes and answers on what they hold. ActiveSupport documents that call as a speedup for empty strings rather than a hook, and `ActiveSupport::SafeBuffer` does not override it.
|
|
68
|
+
|
|
69
|
+
### String#ascii_blank?
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
require 'sin_fast_blank'
|
|
73
|
+
|
|
74
|
+
''.ascii_blank? # => true
|
|
75
|
+
' '.ascii_blank? # => true
|
|
76
|
+
"\t".ascii_blank? # => true
|
|
77
|
+
"\r".ascii_blank? # => true
|
|
78
|
+
"\n".ascii_blank? # => true
|
|
79
|
+
"\r\n".ascii_blank? # => true
|
|
80
|
+
"\r\n\v\f\r\s\t".ascii_blank? # => true
|
|
81
|
+
'abc'.ascii_blank? # => false
|
|
82
|
+
' abc '.ascii_blank? # => false
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Only ASCII whitespace counts, so `String#ascii_blank?` never raises: bytes that form no character are not ASCII whitespace either, and `"\xFF".ascii_blank?` simply returns `false`.
|
|
86
|
+
|
|
87
|
+
## Benchmark
|
|
88
|
+
|
|
89
|
+
SinFastBlank's String#blank? is about 1.3-16.3x faster than FastBlank's String#blank_as? and about 1.2-23.0x faster than ActiveSupport's String#blank?. SinFastBlank's String#ascii_blank? is about 1.2-23.2x faster than FastBlank's String#blank?. At string length 123 the measurement does not separate String#blank? from FastBlank's String#blank_as?, so both are reported as Fastest there.
|
|
90
|
+
|
|
91
|
+
Additionally, this gem allocates no strings during the check, making it less of a burden on the GC.
|
|
92
|
+
|
|
93
|
+
Each string length is reported as two tables because the groups answer different questions: the ActiveSupport-compatible group calls U+3000 and U+00A0 blank, while the ASCII-only pair returns false for both. Ranking them together would compare methods that never did the same work.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
$ bundle exec rake benchmark
|
|
97
|
+
Benchmarking string length: 0...
|
|
98
|
+
Benchmarking string length: 8...
|
|
99
|
+
Benchmarking string length: 43...
|
|
100
|
+
Benchmarking string length: 123...
|
|
101
|
+
Benchmarking string length: 127...
|
|
102
|
+
Benchmarking string length: 238...
|
|
103
|
+
|
|
104
|
+
+-----------------------------------------------------------------------+
|
|
105
|
+
| Benchmark Result (String Length: 0, ActiveSupport-compatible) |
|
|
106
|
+
+------------------------+----------------------+--------+--------------+
|
|
107
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
108
|
+
+------------------------+----------------------+--------+--------------+
|
|
109
|
+
| SinFastBlank - blank? | 189268656.7 | ±0.48% | Fastest |
|
|
110
|
+
| Scratch - blank_e? | 159245632.6 | ±0.42% | 1.2x slower |
|
|
111
|
+
| Scratch - blank_g? | 159177797.8 | ±0.51% | 1.2x slower |
|
|
112
|
+
| ActiveSupport - blank? | 159005067.3 | ±1.02% | 1.2x slower |
|
|
113
|
+
| Scratch - blank_f? | 158579113.9 | ±1.72% | 1.2x slower |
|
|
114
|
+
| Scratch - blank_h? | 158312815.2 | ±0.38% | 1.2x slower |
|
|
115
|
+
| FastBlank - blank_as? | 146149656.1 | ±2.58% | 1.3x slower |
|
|
116
|
+
| Scratch - blank_a? | 24506273.5 | ±0.37% | 7.7x slower |
|
|
117
|
+
| Scratch - blank_c? | 11745600.3 | ±0.23% | 16.1x slower |
|
|
118
|
+
| Scratch - blank_b? | 10570972.1 | ±0.56% | 17.9x slower |
|
|
119
|
+
| Scratch - blank_d? | 4905509.6 | ±0.33% | 38.6x slower |
|
|
120
|
+
+------------------------+----------------------+--------+--------------+
|
|
121
|
+
|
|
122
|
+
+---------------------------------------------------------------------------+
|
|
123
|
+
| Benchmark Result (String Length: 0, ASCII-only) |
|
|
124
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
125
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
126
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
127
|
+
| SinFastBlank - ascii_blank? | 203193300.1 | ±1.09% | Fastest |
|
|
128
|
+
| FastBlank - blank? | 147055633.6 | ±0.60% | 1.4x slower |
|
|
129
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
130
|
+
|
|
131
|
+
+-----------------------------------------------------------------------+
|
|
132
|
+
| Benchmark Result (String Length: 8, ActiveSupport-compatible) |
|
|
133
|
+
+------------------------+----------------------+--------+--------------+
|
|
134
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
135
|
+
+------------------------+----------------------+--------+--------------+
|
|
136
|
+
| SinFastBlank - blank? | 77064598.2 | ±0.31% | Fastest |
|
|
137
|
+
| FastBlank - blank_as? | 27870007.6 | ±0.62% | 2.8x slower |
|
|
138
|
+
| Scratch - blank_c? | 14118003.3 | ±0.26% | 5.5x slower |
|
|
139
|
+
| ActiveSupport - blank? | 13698901.0 | ±0.52% | 5.6x slower |
|
|
140
|
+
| Scratch - blank_g? | 13635284.9 | ±0.20% | 5.7x slower |
|
|
141
|
+
| Scratch - blank_b? | 10198011.1 | ±2.15% | 7.6x slower |
|
|
142
|
+
| Scratch - blank_f? | 10142164.2 | ±0.32% | 7.6x slower |
|
|
143
|
+
| Scratch - blank_a? | 6783807.1 | ±0.98% | 11.4x slower |
|
|
144
|
+
| Scratch - blank_e? | 6521011.0 | ±0.29% | 11.8x slower |
|
|
145
|
+
| Scratch - blank_d? | 5215856.9 | ±1.46% | 14.8x slower |
|
|
146
|
+
| Scratch - blank_h? | 5166323.2 | ±1.75% | 14.9x slower |
|
|
147
|
+
+------------------------+----------------------+--------+--------------+
|
|
148
|
+
|
|
149
|
+
+---------------------------------------------------------------------------+
|
|
150
|
+
| Benchmark Result (String Length: 8, ASCII-only) |
|
|
151
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
152
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
153
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
154
|
+
| SinFastBlank - ascii_blank? | 89085855.6 | ±0.19% | Fastest |
|
|
155
|
+
| FastBlank - blank? | 31180410.5 | ±0.32% | 2.9x slower |
|
|
156
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
157
|
+
|
|
158
|
+
+-----------------------------------------------------------------------+
|
|
159
|
+
| Benchmark Result (String Length: 43, ActiveSupport-compatible) |
|
|
160
|
+
+------------------------+----------------------+--------+--------------+
|
|
161
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
162
|
+
+------------------------+----------------------+--------+--------------+
|
|
163
|
+
| SinFastBlank - blank? | 80570773.4 | ±0.17% | Fastest |
|
|
164
|
+
| FastBlank - blank_as? | 4951349.2 | ±0.21% | 16.3x slower |
|
|
165
|
+
| Scratch - blank_b? | 4572305.5 | ±1.00% | 17.6x slower |
|
|
166
|
+
| Scratch - blank_f? | 4437925.4 | ±1.15% | 18.2x slower |
|
|
167
|
+
| Scratch - blank_g? | 3531002.2 | ±0.84% | 22.8x slower |
|
|
168
|
+
| Scratch - blank_c? | 3523113.8 | ±1.96% | 22.9x slower |
|
|
169
|
+
| ActiveSupport - blank? | 3510419.5 | ±2.05% | 23.0x slower |
|
|
170
|
+
| Scratch - blank_h? | 2976077.1 | ±0.52% | 27.1x slower |
|
|
171
|
+
| Scratch - blank_d? | 2922150.2 | ±2.47% | 27.6x slower |
|
|
172
|
+
| Scratch - blank_a? | 2275385.3 | ±1.61% | 35.4x slower |
|
|
173
|
+
| Scratch - blank_e? | 2257794.6 | ±1.32% | 35.7x slower |
|
|
174
|
+
+------------------------+----------------------+--------+--------------+
|
|
175
|
+
|
|
176
|
+
+----------------------------------------------------------------------------+
|
|
177
|
+
| Benchmark Result (String Length: 43, ASCII-only) |
|
|
178
|
+
+-----------------------------+----------------------+--------+--------------+
|
|
179
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
180
|
+
+-----------------------------+----------------------+--------+--------------+
|
|
181
|
+
| SinFastBlank - ascii_blank? | 105229741.2 | ±0.55% | Fastest |
|
|
182
|
+
| FastBlank - blank? | 4543568.4 | ±3.49% | 23.2x slower |
|
|
183
|
+
+-----------------------------+----------------------+--------+--------------+
|
|
184
|
+
|
|
185
|
+
+-----------------------------------------------------------------------+
|
|
186
|
+
| Benchmark Result (String Length: 123, ActiveSupport-compatible) |
|
|
187
|
+
+------------------------+----------------------+--------+--------------+
|
|
188
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
189
|
+
+------------------------+----------------------+--------+--------------+
|
|
190
|
+
| SinFastBlank - blank? | 96936949.7 | ±3.02% | Fastest |
|
|
191
|
+
| FastBlank - blank_as? | 93386361.9 | ±0.75% | Fastest |
|
|
192
|
+
| Scratch - blank_a? | 26971080.4 | ±2.73% | 3.6x slower |
|
|
193
|
+
| Scratch - blank_e? | 25639570.3 | ±0.18% | 3.8x slower |
|
|
194
|
+
| Scratch - blank_c? | 18687829.5 | ±0.25% | 5.2x slower |
|
|
195
|
+
| Scratch - blank_g? | 17894413.2 | ±0.33% | 5.4x slower |
|
|
196
|
+
| ActiveSupport - blank? | 16714938.4 | ±4.62% | 5.8x slower |
|
|
197
|
+
| Scratch - blank_d? | 9048125.6 | ±0.37% | 10.7x slower |
|
|
198
|
+
| Scratch - blank_h? | 8987475.0 | ±0.68% | 10.8x slower |
|
|
199
|
+
| Scratch - blank_b? | 5352834.6 | ±0.37% | 18.1x slower |
|
|
200
|
+
| Scratch - blank_f? | 5191973.0 | ±0.67% | 18.7x slower |
|
|
201
|
+
+------------------------+----------------------+--------+--------------+
|
|
202
|
+
|
|
203
|
+
+---------------------------------------------------------------------------+
|
|
204
|
+
| Benchmark Result (String Length: 123, ASCII-only) |
|
|
205
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
206
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
207
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
208
|
+
| SinFastBlank - ascii_blank? | 120606725.9 | ±0.94% | Fastest |
|
|
209
|
+
| FastBlank - blank? | 102556412.3 | ±0.20% | 1.2x slower |
|
|
210
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
211
|
+
|
|
212
|
+
+-----------------------------------------------------------------------+
|
|
213
|
+
| Benchmark Result (String Length: 127, ActiveSupport-compatible) |
|
|
214
|
+
+------------------------+----------------------+--------+--------------+
|
|
215
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
216
|
+
+------------------------+----------------------+--------+--------------+
|
|
217
|
+
| SinFastBlank - blank? | 80551912.4 | ±0.35% | Fastest |
|
|
218
|
+
| FastBlank - blank_as? | 42206464.1 | ±0.96% | 1.9x slower |
|
|
219
|
+
| Scratch - blank_c? | 12828245.1 | ±0.23% | 6.3x slower |
|
|
220
|
+
| ActiveSupport - blank? | 12485897.9 | ±0.52% | 6.5x slower |
|
|
221
|
+
| Scratch - blank_g? | 12442303.6 | ±0.23% | 6.5x slower |
|
|
222
|
+
| Scratch - blank_a? | 10419013.7 | ±0.16% | 7.7x slower |
|
|
223
|
+
| Scratch - blank_e? | 10168685.4 | ±0.27% | 7.9x slower |
|
|
224
|
+
| Scratch - blank_d? | 7442964.2 | ±0.58% | 10.8x slower |
|
|
225
|
+
| Scratch - blank_h? | 7397822.4 | ±0.27% | 10.9x slower |
|
|
226
|
+
| Scratch - blank_b? | 5286423.4 | ±0.33% | 15.2x slower |
|
|
227
|
+
| Scratch - blank_f? | 5207047.6 | ±0.39% | 15.5x slower |
|
|
228
|
+
+------------------------+----------------------+--------+--------------+
|
|
229
|
+
|
|
230
|
+
+---------------------------------------------------------------------------+
|
|
231
|
+
| Benchmark Result (String Length: 127, ASCII-only) |
|
|
232
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
233
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
234
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
235
|
+
| SinFastBlank - ascii_blank? | 120881119.0 | ±0.24% | Fastest |
|
|
236
|
+
| FastBlank - blank? | 45716325.1 | ±0.32% | 2.6x slower |
|
|
237
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
238
|
+
|
|
239
|
+
+-----------------------------------------------------------------------+
|
|
240
|
+
| Benchmark Result (String Length: 238, ActiveSupport-compatible) |
|
|
241
|
+
+------------------------+----------------------+--------+--------------+
|
|
242
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
243
|
+
+------------------------+----------------------+--------+--------------+
|
|
244
|
+
| SinFastBlank - blank? | 80596263.4 | ±0.20% | Fastest |
|
|
245
|
+
| FastBlank - blank_as? | 42305937.1 | ±0.20% | 1.9x slower |
|
|
246
|
+
| Scratch - blank_c? | 11596439.7 | ±0.33% | 7.0x slower |
|
|
247
|
+
| ActiveSupport - blank? | 11307320.4 | ±0.51% | 7.1x slower |
|
|
248
|
+
| Scratch - blank_g? | 11288221.5 | ±0.24% | 7.1x slower |
|
|
249
|
+
| Scratch - blank_a? | 9202475.9 | ±0.26% | 8.8x slower |
|
|
250
|
+
| Scratch - blank_e? | 9013830.2 | ±0.28% | 8.9x slower |
|
|
251
|
+
| Scratch - blank_d? | 7162431.6 | ±0.57% | 11.3x slower |
|
|
252
|
+
| Scratch - blank_h? | 7119191.1 | ±1.11% | 11.3x slower |
|
|
253
|
+
| Scratch - blank_b? | 3924922.1 | ±0.78% | 20.5x slower |
|
|
254
|
+
| Scratch - blank_f? | 3859441.2 | ±0.38% | 20.9x slower |
|
|
255
|
+
+------------------------+----------------------+--------+--------------+
|
|
256
|
+
|
|
257
|
+
+---------------------------------------------------------------------------+
|
|
258
|
+
| Benchmark Result (String Length: 238, ASCII-only) |
|
|
259
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
260
|
+
| Name | Iteration Per Second | Error | Speed Ratio |
|
|
261
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
262
|
+
| SinFastBlank - ascii_blank? | 120877343.1 | ±0.18% | Fastest |
|
|
263
|
+
| FastBlank - blank? | 45726024.3 | ±0.20% | 2.6x slower |
|
|
264
|
+
+-----------------------------+----------------------+--------+-------------+
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Each row's error is how much its own measurement moved within that run. Across runs the whole machine drifts further than that, so the ratios above move by a few tenths from one run to the next.
|
|
268
|
+
|
|
269
|
+
The error is also what decides the Speed Ratio column: a row is labelled Fastest when its gap to the top row is narrower than the two errors together, because a gap that small is the measurement failing to separate them rather than a difference it found. More than one row can therefore read Fastest.
|
|
270
|
+
|
|
271
|
+
Performance depends not only on the string length but also on its content.
|
|
272
|
+
|
|
273
|
+
The benchmark was executed in the following environment:
|
|
274
|
+
|
|
275
|
+
- Ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin25]
|
|
276
|
+
- FastBlank 1.0.1
|
|
277
|
+
- ActiveSupport 8.1.3.1
|
|
278
|
+
|
|
279
|
+
## Development
|
|
280
|
+
|
|
281
|
+
### Building for JRuby
|
|
282
|
+
|
|
283
|
+
To build the Java extension and run tests for JRuby support:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
./script/jruby_build_and_test.sh
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Changelog
|
|
290
|
+
|
|
291
|
+
See [CHANGELOG.md](https://github.com/cadenza-tech/sin_fast_blank/blob/main/CHANGELOG.md).
|
|
292
|
+
|
|
293
|
+
## Contributing
|
|
294
|
+
|
|
295
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cadenza-tech/sin_fast_blank. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/cadenza-tech/sin_fast_blank/blob/main/CODE_OF_CONDUCT.md).
|
|
296
|
+
|
|
297
|
+
## License
|
|
298
|
+
|
|
299
|
+
The gem is available as open source under the terms of the [MIT License](https://github.com/cadenza-tech/sin_fast_blank/blob/main/LICENSE.txt).
|
|
300
|
+
|
|
301
|
+
## Code of Conduct
|
|
302
|
+
|
|
303
|
+
Everyone interacting in the SinFastBlank project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/cadenza-tech/sin_fast_blank/blob/main/CODE_OF_CONDUCT.md).
|
|
304
|
+
|
|
305
|
+
## Sponsor
|
|
306
|
+
|
|
307
|
+
You can sponsor this project on [GitHub Sponsors](https://github.com/sponsors/cadenza-tech).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rake/testtask'
|
|
5
|
+
require 'rubocop/rake_task'
|
|
6
|
+
require 'rubygems/package_task'
|
|
7
|
+
|
|
8
|
+
gemspec = Gem::Specification.load("#{__dir__}/sin_fast_blank.gemspec")
|
|
9
|
+
|
|
10
|
+
if RUBY_ENGINE == 'jruby'
|
|
11
|
+
require 'rake/javaextensiontask'
|
|
12
|
+
|
|
13
|
+
Rake::JavaExtensionTask.new('sin_fast_blank', gemspec) do |task|
|
|
14
|
+
task.ext_dir = 'ext/java'
|
|
15
|
+
task.source_version = '1.8'
|
|
16
|
+
task.target_version = '1.8'
|
|
17
|
+
end
|
|
18
|
+
else
|
|
19
|
+
require 'rake/extensiontask'
|
|
20
|
+
|
|
21
|
+
Rake::ExtensionTask.new('sin_fast_blank', gemspec) do |task|
|
|
22
|
+
task.lib_dir = 'lib/sin_fast_blank'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Gem::PackageTask.new(gemspec)
|
|
27
|
+
|
|
28
|
+
Rake::TestTask.new(:test) do |task|
|
|
29
|
+
task.pattern = 'test/**/test_*.rb'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Without this the suite happily runs against a stale binary, so a changed extension looks like it passed.
|
|
33
|
+
Rake::Task[:test].enhance([:compile])
|
|
34
|
+
|
|
35
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
|
36
|
+
task.options = ['--format', ENV['RUBOCOP_FORMAT']] if ENV['RUBOCOP_FORMAT']
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
task benchmark: [:compile] do
|
|
40
|
+
RubyVM::YJIT.enable if defined?(RubyVM::YJIT) && !RubyVM::YJIT.enabled?
|
|
41
|
+
|
|
42
|
+
require_relative 'script/blank_benchmark'
|
|
43
|
+
|
|
44
|
+
BlankBenchmark.run
|
|
45
|
+
end
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
require 'mkmf'
|
|
4
4
|
|
|
5
|
+
# Compile-wide -mavx2 bakes AVX2 into the binary and crashes with SIGILL on CPUs without it
|
|
6
|
+
# (pre-Haswell, QEMU defaults, some VPS), so probe for per-function AVX2 + runtime detection and let
|
|
7
|
+
# Init_sin_fast_blank choose. try_link, not try_compile: __builtin_cpu_supports needs link-time runtime support.
|
|
8
|
+
AVX2_RUNTIME_DISPATCH_PROBE = <<~SRC
|
|
9
|
+
#include <immintrin.h>
|
|
10
|
+
__attribute__((target("avx2"))) static int avx2_probe(void) {
|
|
11
|
+
__m256i zero = _mm256_setzero_si256();
|
|
12
|
+
return _mm256_movemask_epi8(_mm256_cmpeq_epi8(zero, zero));
|
|
13
|
+
}
|
|
14
|
+
int main(void) {
|
|
15
|
+
return __builtin_cpu_supports("avx2") ? avx2_probe() : 0;
|
|
16
|
+
}
|
|
17
|
+
SRC
|
|
18
|
+
|
|
5
19
|
old_truffleruby = false
|
|
6
20
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'truffleruby' && defined?(RUBY_ENGINE_VERSION)
|
|
7
21
|
major_version = RUBY_ENGINE_VERSION.split('.').first.to_i
|
|
@@ -11,15 +25,26 @@ end
|
|
|
11
25
|
unless old_truffleruby
|
|
12
26
|
case RbConfig::CONFIG['host_cpu']
|
|
13
27
|
when /x86_64|i[3-6]86/
|
|
14
|
-
|
|
15
|
-
|
|
28
|
+
# SSE2 is part of the x86_64 ABI, so -msse2 is always safe there. On 32-bit x86 it is
|
|
29
|
+
# still compiled in unconditionally (pre-SSE2 CPUs would need a runtime dispatch as well).
|
|
30
|
+
# append_cflags, not $CFLAGS <<: MSVC does not know the flag and would only warn about it,
|
|
31
|
+
# and it does not need it either, since sin_fast_blank.c reads _M_X64 to reach the same conclusion.
|
|
32
|
+
append_cflags('-msse2')
|
|
33
|
+
$CFLAGS << ' -DHAVE_AVX2_RUNTIME_DISPATCH' if checking_for('AVX2 runtime dispatch') { try_link(AVX2_RUNTIME_DISPATCH_PROBE) }
|
|
16
34
|
when /aarch64|arm64/
|
|
17
35
|
# No special flags needed as NEON is enabled by default on ARM64
|
|
18
36
|
when /arm/
|
|
19
|
-
|
|
37
|
+
# Nothing to add. The NEON chunk helpers reduce with vminvq_u8, which only AArch64 has, so
|
|
38
|
+
# sin_fast_blank.c gates them on __aarch64__ and 32-bit ARM stays on the scalar scan whatever
|
|
39
|
+
# -mfpu says. Handing it -mfpu=neon would compile that same scan into a binary that no longer
|
|
40
|
+
# runs on a NEON-less ARMv7.
|
|
20
41
|
end
|
|
21
42
|
end
|
|
22
43
|
|
|
23
|
-
|
|
44
|
+
# rb_ext_ractor_safe() arrived in Ruby 3.0 and the gem still supports 2.3, so Init_sin_fast_blank guards its call on this.
|
|
45
|
+
have_func('rb_ext_ractor_safe')
|
|
46
|
+
|
|
47
|
+
# Both are GCC/Clang spellings. MSVC ignores them with a warning and keeps the /O2 Ruby already passes.
|
|
48
|
+
append_cflags(['-O3', '-funroll-loops'])
|
|
24
49
|
|
|
25
|
-
create_makefile 'sin_fast_blank'
|
|
50
|
+
create_makefile 'sin_fast_blank/sin_fast_blank'
|