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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 715b972c44a78f3a18dcee12c9133c5b38acce03a76fee4031df21d3c5ec8b2f
4
- data.tar.gz: bce4b9d3bd8058ab567c620f0cd684f817f4193b20271e481c25a0560954631b
3
+ metadata.gz: 85588dad4cdf2b592985b87f2119e5ace5a062e9a36bd2cf2ea760b499781112
4
+ data.tar.gz: bcc255feaac9280e816034458ffe39cad5fc954df31ef247e4d6da6cbe85c487
5
5
  SHA512:
6
- metadata.gz: c8643cd88797cb3d5824536ddfc7f60b4f7bf5718a20a3464b9231720c50e0558fa5234c1f23f0adcb1a8b31664d3a4bb25c3209545619ea0a00e1f5e89408bf
7
- data.tar.gz: 14d825f306f16680badaab6c8a1153ef817f889f73ad45169ec6069abf67f25358ed4324c4184c742760cb198cd2066ad32f249fd71e67ddbeafbd755ee2c88f
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
+ [![License](https://img.shields.io/github/license/cadenza-tech/sin_fast_blank?label=License&labelColor=343B42&color=blue)](https://github.com/cadenza-tech/sin_fast_blank/blob/main/LICENSE.txt) [![Tag](https://img.shields.io/github/tag/cadenza-tech/sin_fast_blank?label=Tag&logo=github&labelColor=343B42&color=2EBC4F)](https://github.com/cadenza-tech/sin_fast_blank/blob/main/CHANGELOG.md) [![Release](https://github.com/cadenza-tech/sin_fast_blank/actions/workflows/release.yml/badge.svg)](https://github.com/cadenza-tech/sin_fast_blank/actions?query=workflow%3Arelease) [![Test](https://github.com/cadenza-tech/sin_fast_blank/actions/workflows/test.yml/badge.svg)](https://github.com/cadenza-tech/sin_fast_blank/actions?query=workflow%3Atest) [![Lint](https://github.com/cadenza-tech/sin_fast_blank/actions/workflows/lint.yml/badge.svg)](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
- $CFLAGS << ' -msse2'
15
- $CFLAGS << ' -mavx2' if have_header('immintrin.h') && try_compile('#include <immintrin.h>')
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
- $CFLAGS << ' -mfpu=neon' if have_header('arm_neon.h') && try_compile('#include <arm_neon.h>')
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
- $CFLAGS << ' -O3 -funroll-loops'
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'