sin_fast_blank 4.0.1-java → 5.0.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.
- 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/java/sin_fast_blank/SinFastBlankLibrary.java +105 -23
- data/lib/sin_fast_blank/sin_fast_blank.jar +0 -0
- data/lib/sin_fast_blank/version.rb +5 -0
- data/lib/sin_fast_blank.rb +19 -0
- metadata +15 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34c933a31a4b5f14ef54f92a544abfef3a09c2463e095fcca3542c6c1abc562a
|
|
4
|
+
data.tar.gz: ebade8139e66100f82cd361591c57e5e53d03d5804258a352d599a9bea87d923
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12adaa05f6ae256d519e0ea822e78aaf9a82466eee457837ace600d34eb3b1f68d52d9a3741ded095645fead9636db2942f9de341e92f0d26165c2432984db22
|
|
7
|
+
data.tar.gz: 36133336ea316b3fcb795e5565f38a9f4c53b666aff85f9212f96085506dbb7229c2ff255b9115381ef09e91d830cc477a747854fe0017df1080185619ed9a97
|
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
|
|
@@ -8,9 +8,14 @@ import org.jruby.runtime.ThreadContext;
|
|
|
8
8
|
import org.jruby.runtime.builtin.IRubyObject;
|
|
9
9
|
import org.jruby.runtime.load.Library;
|
|
10
10
|
import org.jruby.util.ByteList;
|
|
11
|
-
import org.jruby.util.
|
|
11
|
+
import org.jruby.util.StringSupport;
|
|
12
12
|
|
|
13
13
|
public class SinFastBlankLibrary implements Library {
|
|
14
|
+
private static final int ASCII_WS_RANGE_MIN = 0x09;
|
|
15
|
+
private static final int ASCII_WS_RANGE_MAX = 0x0d;
|
|
16
|
+
private static final int ASCII_WS_SPACE = 0x20;
|
|
17
|
+
private static final int MAX_CTYPE_CODEPOINT = 0xFF;
|
|
18
|
+
|
|
14
19
|
@Override
|
|
15
20
|
public void load(Ruby runtime, boolean wrap) {
|
|
16
21
|
runtime.getString().defineAnnotatedMethods(SinFastBlankLibrary.class);
|
|
@@ -33,7 +38,7 @@ public class SinFastBlankLibrary implements Library {
|
|
|
33
38
|
for (int i = s; i < e; i++) {
|
|
34
39
|
byte c = bytes[i];
|
|
35
40
|
if (c < 0) {
|
|
36
|
-
return
|
|
41
|
+
return blankMixed(context, str, bytes, i, e, enc);
|
|
37
42
|
}
|
|
38
43
|
if (!isAsciiBlank(c)) {
|
|
39
44
|
return context.fals;
|
|
@@ -42,11 +47,11 @@ public class SinFastBlankLibrary implements Library {
|
|
|
42
47
|
return context.tru;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
return blankUnicodeSlow(context, bytes, s, e, enc);
|
|
50
|
+
return blankUnicodeSlow(context, str, bytes, s, e, enc);
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
private static boolean isAsciiBlank(byte c) {
|
|
49
|
-
return (c >=
|
|
54
|
+
return (c >= ASCII_WS_RANGE_MIN && c <= ASCII_WS_RANGE_MAX) || c == ASCII_WS_SPACE;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
private static boolean isUnicodeBlank(int codepoint) {
|
|
@@ -82,22 +87,93 @@ public class SinFastBlankLibrary implements Library {
|
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
/*
|
|
91
|
+
* ActiveSupport's blank regexp matches [[:space:]] with the ctype table of the string's own
|
|
92
|
+
* encoding, which differs from the Unicode table for some encodings (e.g. 0x85 is blank in
|
|
93
|
+
* UTF-8 but not in ISO-8859-1 or ASCII-8BIT). isSpace() reads that same table.
|
|
94
|
+
*
|
|
95
|
+
* Only single-byte codes reach it, and that is not an optimization. Emacs-Mule and the
|
|
96
|
+
* stateless ISO-2022-JP variants answer every ctype query for a multi-byte code with true
|
|
97
|
+
* (EmacsMuleEncoding returns codeToMbcLength(code) > 1 whatever ctype is asked for), and wide
|
|
98
|
+
* GB18030 codepoints arrive negative in a signed int. Falling back to the switch loses
|
|
99
|
+
* nothing: in an ASCII-compatible encoding a multi-byte codepoint always starts at 0x8000 or
|
|
100
|
+
* above, past the U+3000 the switch tops out at, so it only ever answers "not blank" there.
|
|
101
|
+
*/
|
|
102
|
+
private static boolean isBlankCodepoint(int codepoint, Encoding enc) {
|
|
103
|
+
if (enc.isUnicode() || codepoint < 0 || codepoint > MAX_CTYPE_CODEPOINT) {
|
|
104
|
+
return isUnicodeBlank(codepoint);
|
|
105
|
+
}
|
|
106
|
+
return enc.isSpace(codepoint);
|
|
107
|
+
}
|
|
89
108
|
|
|
109
|
+
/*
|
|
110
|
+
* Handles the rest of an ASCII-compatible string once its first non-ASCII byte is found: ASCII
|
|
111
|
+
* runs stay on the bytewise scan and only non-ASCII characters go through the decoder. The
|
|
112
|
+
* decoder is entered only on character boundaries: every byte the bytewise scan steps over is
|
|
113
|
+
* a single-byte character, and decoded characters are skipped whole.
|
|
114
|
+
*
|
|
115
|
+
* blankUnicodeSlow stays for what this cannot serve: where the encoding is not
|
|
116
|
+
* ASCII-compatible, a byte below 0x80 is not a character on its own, so every character has to
|
|
117
|
+
* go through the decoder.
|
|
118
|
+
*/
|
|
119
|
+
private static IRubyObject blankMixed(
|
|
120
|
+
ThreadContext context, RubyString str, byte[] bytes, int s, int e, Encoding enc) {
|
|
90
121
|
while (s < e) {
|
|
91
|
-
|
|
92
|
-
if (
|
|
122
|
+
byte c = bytes[s];
|
|
123
|
+
if (c >= 0) {
|
|
124
|
+
if (!isAsciiBlank(c)) {
|
|
125
|
+
return context.fals;
|
|
126
|
+
}
|
|
127
|
+
s++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
int length = StringSupport.preciseLength(enc, bytes, s, e);
|
|
131
|
+
if (!StringSupport.MBCLEN_CHARFOUND_P(length)) {
|
|
132
|
+
return blankUndecodable(context, str, enc);
|
|
133
|
+
}
|
|
134
|
+
int codepoint = enc.mbcToCode(bytes, s, e);
|
|
135
|
+
if (!isBlankCodepoint(codepoint, enc)) {
|
|
93
136
|
return context.fals;
|
|
94
137
|
}
|
|
95
|
-
s +=
|
|
138
|
+
s += StringSupport.MBCLEN_CHARFOUND_LEN(length);
|
|
96
139
|
}
|
|
97
140
|
|
|
98
141
|
return context.tru;
|
|
99
142
|
}
|
|
100
143
|
|
|
144
|
+
private static IRubyObject blankUnicodeSlow(
|
|
145
|
+
ThreadContext context, RubyString str, byte[] bytes, int s, int e, Encoding enc) {
|
|
146
|
+
while (s < e) {
|
|
147
|
+
int length = StringSupport.preciseLength(enc, bytes, s, e);
|
|
148
|
+
if (!StringSupport.MBCLEN_CHARFOUND_P(length)) {
|
|
149
|
+
return blankUndecodable(context, str, enc);
|
|
150
|
+
}
|
|
151
|
+
int codepoint = enc.mbcToCode(bytes, s, e);
|
|
152
|
+
if (!isBlankCodepoint(codepoint, enc)) {
|
|
153
|
+
return context.fals;
|
|
154
|
+
}
|
|
155
|
+
s += StringSupport.MBCLEN_CHARFOUND_LEN(length);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return context.tru;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
* Reached when the scanner cannot decode the bytes ahead. ActiveSupport's regexp never rescans:
|
|
163
|
+
* it trusts the code range Ruby cached on the string, so it raises only for a string Ruby itself
|
|
164
|
+
* calls broken, and answers "not blank" for one whose transcoder and scanner disagree. Reading
|
|
165
|
+
* the cached code range costs nothing; computing an uncomputed one would scan the whole string,
|
|
166
|
+
* give up the early exit this loop exists for, and only ever come out broken anyway, since it
|
|
167
|
+
* runs the decode that just failed here.
|
|
168
|
+
*/
|
|
169
|
+
private static IRubyObject blankUndecodable(
|
|
170
|
+
ThreadContext context, RubyString str, Encoding enc) {
|
|
171
|
+
if (str.getCodeRange() == StringSupport.CR_VALID) {
|
|
172
|
+
return context.fals;
|
|
173
|
+
}
|
|
174
|
+
throw context.runtime.newArgumentError("invalid byte sequence in " + enc);
|
|
175
|
+
}
|
|
176
|
+
|
|
101
177
|
@JRubyMethod(name = "ascii_blank?")
|
|
102
178
|
public static IRubyObject ascii_blank_p(ThreadContext context, IRubyObject self) {
|
|
103
179
|
RubyString str = (RubyString) self;
|
|
@@ -111,13 +187,15 @@ public class SinFastBlankLibrary implements Library {
|
|
|
111
187
|
int e = s + byteList.realSize();
|
|
112
188
|
Encoding enc = str.getEncoding();
|
|
113
189
|
|
|
190
|
+
/*
|
|
191
|
+
* This one never raises, because bytes that decode to nothing are no more an ASCII blank
|
|
192
|
+
* than the characters they failed to form. An ASCII-compatible encoding does not even need
|
|
193
|
+
* the decoder: a byte of 0x80 or above only ever starts a character whose codepoint is 0x80
|
|
194
|
+
* or above there, so the first one settles the answer.
|
|
195
|
+
*/
|
|
114
196
|
if (enc.isAsciiCompatible()) {
|
|
115
197
|
for (int i = s; i < e; i++) {
|
|
116
|
-
|
|
117
|
-
if (c < 0) {
|
|
118
|
-
return asciiBlankUnicodeSlow(context, bytes, i, e, enc);
|
|
119
|
-
}
|
|
120
|
-
if (!isAsciiBlankOrNull(c)) {
|
|
198
|
+
if (!isAsciiBlankOrNull(bytes[i])) {
|
|
121
199
|
return context.fals;
|
|
122
200
|
}
|
|
123
201
|
}
|
|
@@ -128,24 +206,28 @@ public class SinFastBlankLibrary implements Library {
|
|
|
128
206
|
}
|
|
129
207
|
|
|
130
208
|
private static boolean isAsciiBlankOrNull(byte c) {
|
|
131
|
-
return c ==
|
|
209
|
+
return c == 0x00
|
|
210
|
+
|| (c >= ASCII_WS_RANGE_MIN && c <= ASCII_WS_RANGE_MAX)
|
|
211
|
+
|| c == ASCII_WS_SPACE;
|
|
132
212
|
}
|
|
133
213
|
|
|
134
214
|
private static boolean isAsciiSpace(int codepoint) {
|
|
135
|
-
return codepoint
|
|
215
|
+
return (codepoint >= ASCII_WS_RANGE_MIN && codepoint <= ASCII_WS_RANGE_MAX)
|
|
216
|
+
|| codepoint == ASCII_WS_SPACE;
|
|
136
217
|
}
|
|
137
218
|
|
|
138
219
|
private static IRubyObject asciiBlankUnicodeSlow(
|
|
139
220
|
ThreadContext context, byte[] bytes, int s, int e, Encoding enc) {
|
|
140
|
-
Ruby runtime = context.runtime;
|
|
141
|
-
int[] len = {0};
|
|
142
|
-
|
|
143
221
|
while (s < e) {
|
|
144
|
-
int
|
|
222
|
+
int length = StringSupport.preciseLength(enc, bytes, s, e);
|
|
223
|
+
if (!StringSupport.MBCLEN_CHARFOUND_P(length)) {
|
|
224
|
+
return context.fals;
|
|
225
|
+
}
|
|
226
|
+
int codepoint = enc.mbcToCode(bytes, s, e);
|
|
145
227
|
if (codepoint != 0 && !isAsciiSpace(codepoint)) {
|
|
146
228
|
return context.fals;
|
|
147
229
|
}
|
|
148
|
-
s +=
|
|
230
|
+
s += StringSupport.MBCLEN_CHARFOUND_LEN(length);
|
|
149
231
|
}
|
|
150
232
|
|
|
151
233
|
return context.tru;
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class String
|
|
4
|
+
# Explicitly undefine method before redefining to avoid Ruby warnings.
|
|
5
|
+
undef_method(:blank?) if method_defined?(:blank?)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
case RUBY_ENGINE
|
|
9
|
+
when 'jruby'
|
|
10
|
+
require 'jruby'
|
|
11
|
+
require 'sin_fast_blank/sin_fast_blank.jar'
|
|
12
|
+
|
|
13
|
+
Java::sin_fast_blank::SinFastBlankLibrary.new.load(JRuby.runtime, false)
|
|
14
|
+
else
|
|
15
|
+
# The extension-less require lets Ruby resolve the platform-specific shared library suffix (.bundle on macOS, .so elsewhere) via DLEXT.
|
|
16
|
+
require 'sin_fast_blank/sin_fast_blank'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
require 'sin_fast_blank/version'
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sin_fast_blank
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 5.0.0
|
|
5
5
|
platform: java
|
|
6
6
|
authors:
|
|
7
7
|
- Masahiro
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: Check for blank string faster than FastBlank or ActiveSupport
|
|
14
13
|
email:
|
|
@@ -17,23 +16,25 @@ executables: []
|
|
|
17
16
|
extensions: []
|
|
18
17
|
extra_rdoc_files: []
|
|
19
18
|
files:
|
|
19
|
+
- CHANGELOG.md
|
|
20
|
+
- LICENSE.txt
|
|
21
|
+
- README.md
|
|
22
|
+
- Rakefile
|
|
20
23
|
- ext/java/sin_fast_blank/SinFastBlankLibrary.java
|
|
24
|
+
- lib/sin_fast_blank.rb
|
|
21
25
|
- lib/sin_fast_blank/sin_fast_blank.jar
|
|
22
|
-
|
|
26
|
+
- lib/sin_fast_blank/version.rb
|
|
27
|
+
homepage: https://github.com/cadenza-tech/sin_fast_blank/tree/v5.0.0
|
|
23
28
|
licenses:
|
|
24
29
|
- MIT
|
|
25
30
|
metadata:
|
|
26
|
-
homepage_uri: https://github.com/cadenza-tech/sin_fast_blank/tree/
|
|
27
|
-
source_code_uri: https://github.com/cadenza-tech/sin_fast_blank/tree/
|
|
28
|
-
changelog_uri: https://github.com/cadenza-tech/sin_fast_blank/blob/
|
|
31
|
+
homepage_uri: https://github.com/cadenza-tech/sin_fast_blank/tree/v5.0.0
|
|
32
|
+
source_code_uri: https://github.com/cadenza-tech/sin_fast_blank/tree/v5.0.0
|
|
33
|
+
changelog_uri: https://github.com/cadenza-tech/sin_fast_blank/blob/v5.0.0/CHANGELOG.md
|
|
29
34
|
bug_tracker_uri: https://github.com/cadenza-tech/sin_fast_blank/issues
|
|
30
|
-
documentation_uri: https://rubydoc.info/gems/sin_fast_blank/
|
|
31
|
-
funding_uri: https://
|
|
35
|
+
documentation_uri: https://rubydoc.info/gems/sin_fast_blank/5.0.0
|
|
36
|
+
funding_uri: https://github.com/sponsors/cadenza-tech
|
|
32
37
|
rubygems_mfa_required: 'true'
|
|
33
|
-
required_jruby_version: ">= 9.3.0.0"
|
|
34
|
-
required_truffleruby_version: ">= 22.0.0"
|
|
35
|
-
required_truffleruby+graalvm_version: ">= 22.0.0"
|
|
36
|
-
post_install_message:
|
|
37
38
|
rdoc_options: []
|
|
38
39
|
require_paths:
|
|
39
40
|
- lib
|
|
@@ -48,8 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
48
49
|
- !ruby/object:Gem::Version
|
|
49
50
|
version: '0'
|
|
50
51
|
requirements: []
|
|
51
|
-
rubygems_version:
|
|
52
|
-
signing_key:
|
|
52
|
+
rubygems_version: 4.0.3
|
|
53
53
|
specification_version: 4
|
|
54
54
|
summary: Check for blank string faster than FastBlank or ActiveSupport
|
|
55
55
|
test_files: []
|