smarter_json 1.2.3 → 1.2.4

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: edd2cbc389a44f0714898f2a2942032632ca5f13a83d93acd708988866974893
4
- data.tar.gz: 5cfa8719265797fd0fcd59f0060a5dd255aeba67ff229c78ab5696ad86f8b8e3
3
+ metadata.gz: 1bdfb87113872a6659af0063f196de84f9074b81eedc90ae63445fa6a9f86a0a
4
+ data.tar.gz: 1271826d3097455921c7f058710ea42651daa75a0de9438b0f0d581e7abc8b7a
5
5
  SHA512:
6
- metadata.gz: b0306cb7eab1db78053e8620f119b7debc54d836c38369b328bc0e0b9e8798d23eb4bbdb82281d036f62cb8cd296a3c8d7667f41a49d7e643a955830e08caf99
7
- data.tar.gz: 0a5f052e115141014bcf2f0e031d43fb40521dd01da0f1ed5c6d4edeea785ead09f8ebcdfb76b580b4b8157391087ff2a79c5cd548ae7d8a0be96d0527a9f8fb
6
+ metadata.gz: 4885f735c132bea79affc74ddb82504c581b722a03f52fbbbaabe27fa818347715bf1753b2fc539fb86991448a3e7987719d679f18210aaab54afbe06ee1d021
7
+ data.tar.gz: c9fafa246a337eb025a089e3db4909c596d52b023b5b06291e7f3b8114f225c57ad4e48b4fd742ffd295db1c95e6cd9574e68adc6bba0d9f5a4c8695b0620649
data/CHANGELOG.md CHANGED
@@ -13,6 +13,18 @@
13
13
  > ⚠️ We discourage the use of `process(input).first` / `process(input)[0]` because it silently drops potential additional documents
14
14
  > Please use `process_one` if you are expecting only one JSON doc, e.g. in API payloads, because it emits on_warning if it finds multiple docs.
15
15
 
16
+ ## 1.2.4 (2026-07-01)
17
+
18
+ RSpec tests: 1,268 → 1,282
19
+
20
+ ### Bug Fixes
21
+
22
+ - **Portable builds by default — fixes the "Illegal instruction" crash on mixed-hardware fleets.** The C extension was compiled with `-march=native` on every platform except Apple Silicon, baking in the build host's CPU instructions (e.g. AVX-512). A binary built on one machine could then crash with `Illegal instruction` when run on a CPU lacking those instructions — common when the build host differs from the run host (CI/build servers, Docker images, mixed-hardware fleets). The C extension is now built **portable** by default (no host-specific instructions).
23
+
24
+ ### New Features
25
+
26
+ - **`SMARTER_JSON_PERFORMANCE` build option** (`portable` default, `tuned`, or `max`) to opt into host-tuned or host-specific instructions at install time. See the [README](README.md#cpu-optimization-smarter_json_performance) / [Introduction](docs/_introduction.md#build-time-performance-tuning-smarter_json_performance) for details.
27
+
16
28
  ## 1.2.3 (2026-06-28)
17
29
 
18
30
  RSpec tests: 1,167 → 1,268
data/README.md CHANGED
@@ -114,6 +114,26 @@ gem install smarter_json
114
114
 
115
115
  The C extension is built on install and used automatically. On platforms where it can't build, the pure-Ruby implementation runs instead and produces identical results.
116
116
 
117
+ ### CPU Optimization (`SMARTER_JSON_PERFORMANCE`)
118
+
119
+ The C extension is compiled when the gem is installed. By default it is built **portable**: it uses no CPU-specific instructions, so a binary built on one machine runs on any other CPU of the same architecture. Set `SMARTER_JSON_PERFORMANCE` at install time to trade portability for speed:
120
+
121
+ | Level | Flags added | Portable? | Use when |
122
+ |----------------------|-------------------------------------------|----------------------------------|---------------------------------------|
123
+ | `portable` (default) | none | Yes, any CPU of the arch | Build host may differ from run host |
124
+ | `tuned` | `-mtune=native` | Yes, instruction scheduling only | Build and run hosts share a microarch |
125
+ | `max` | `-march=native`, or `-mcpu=native` on ARM | No, host instruction optimization| Build host and run host are the same |
126
+
127
+ `max` enables host-specific instructions, so a binary built with it can crash with `Illegal instruction` if it later runs on a CPU that lacks them (for example, built on an AVX-512 machine and run on one without). `tuned` only changes instruction scheduling, never the instruction set, so it stays portable. Every flag is probed against your compiler at build time and skipped if unsupported, so an unavailable flag never breaks the build.
128
+
129
+ ```bash
130
+ SMARTER_JSON_PERFORMANCE=tuned gem install smarter_json # portable, tuned for this machine's microarchitecture
131
+ SMARTER_JSON_PERFORMANCE=max gem install smarter_json # fastest, NOT portable — only when you build on the machine you run on
132
+ SMARTER_JSON_PERFORMANCE=tuned bundle install # same, under Bundler
133
+ ```
134
+
135
+ For a fixed baseline instead of `native` (e.g. a portable-but-newer instruction set), pass flags directly via `CFLAGS`, which the build also honors: `CFLAGS="-march=x86-64-v2" gem install smarter_json`.
136
+
117
137
  ## Usage
118
138
 
119
139
  Pass a String of JSON content or an IO; you get back the extracted data. The same call handles strict JSON, JSON5, and HJSON-style config — there are no modes or flags.
@@ -37,6 +37,28 @@ It raises only on genuinely unreadable input (unterminated string, mismatched br
37
37
 
38
38
  Both the C extension and the pure-Ruby engine are **iterative, not recursive** — they track nesting on an explicit, heap-allocated stack rather than the call stack. So deeply nested input **cannot overflow the call stack or segfault**: nesting is bounded only by available memory, the same posture as Oj (the stdlib `json` caps at 100). The trade-off: there is currently **no fixed nesting or input-size limit**, so size-limit untrusted input upstream.
39
39
 
40
+ ## Build-Time Performance Tuning (`SMARTER_JSON_PERFORMANCE`)
41
+
42
+ The C extension is compiled when the gem is installed. By default it is built **portable**: it uses no CPU-specific instructions, so a binary compiled on one machine runs on any other CPU of the same architecture. This matters whenever the machine that builds the gem differs from the machine that runs it — a CI or build server, a Docker image moved between hosts, or a mixed-hardware fleet. A build that bakes in instructions the run host lacks (such as AVX-512) would otherwise crash with `Illegal instruction`.
43
+
44
+ Set `SMARTER_JSON_PERFORMANCE` at install time to trade portability for speed:
45
+
46
+ | Level | Flags added | Portable? | Use when |
47
+ |----------------------|-------------------------------------------|----------------------------------|---------------------------------------|
48
+ | `portable` (default) | none | Yes, any CPU of the arch | Build host may differ from run host |
49
+ | `tuned` | `-mtune=native` | Yes, instruction scheduling only | Build and run hosts share a microarch |
50
+ | `max` | `-march=native`, or `-mcpu=native` on ARM | No, host instruction optimization| Build host and run host are the same |
51
+
52
+ `tuned` only changes instruction scheduling, never the instruction set, so it stays portable — and it pays off when the build and run hosts share a microarchitecture (the same chip, or a fleet of identical instances). `max` enables host-specific instructions and is the fastest, but a binary built with it can crash on a different CPU. Every flag is probed against your compiler at build time and skipped if unsupported, so an unavailable flag never breaks the build.
53
+
54
+ ```bash
55
+ SMARTER_JSON_PERFORMANCE=tuned gem install smarter_json # portable, tuned for this machine's microarchitecture
56
+ SMARTER_JSON_PERFORMANCE=max gem install smarter_json # fastest, NOT portable — only when you build on the machine you run on
57
+ SMARTER_JSON_PERFORMANCE=tuned bundle install # same, under Bundler
58
+ ```
59
+
60
+ For a fixed baseline instead of `native` (e.g. a portable-but-newer instruction set), pass flags directly via `CFLAGS`, which the build also honors: `CFLAGS="-march=x86-64-v2" gem install smarter_json`.
61
+
40
62
  ---------------
41
63
 
42
64
  NEXT: [The Basic Read API](./basic_read_api.md) | UP: [README](../README.md)
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmarterJSON
4
+ # Pure (mkmf-free) selection of CPU-optimization flags from the
5
+ # SMARTER_JSON_PERFORMANCE environment variable. Kept separate from extconf.rb
6
+ # so the logic can be unit-tested without invoking a compiler.
7
+ #
8
+ # Levels:
9
+ # portable - no host-specific flags. The binary runs on any CPU of the same
10
+ # architecture. The safe default: a binary built here will not
11
+ # crash with "Illegal instruction" on an older/different CPU.
12
+ # tuned - -mtune=native: tunes instruction scheduling for the build host's
13
+ # microarchitecture WITHOUT changing the instruction set, so the
14
+ # binary stays portable. A real win when build and run hosts share
15
+ # a microarchitecture (same chip or a homogeneous fleet).
16
+ # max - host-specific instructions: -march=native, or -mcpu=native on
17
+ # ARM/Clang where -march=native is rejected. Fastest, but NOT
18
+ # portable -- may crash on a CPU lacking the build host's
19
+ # instructions. Use only when build host and run host match.
20
+ #
21
+ # `accepts` is a predicate (in the real build, a wrapper over mkmf's
22
+ # try_compile) returning true when the compiler accepts a given flag; each
23
+ # candidate is probed so an unsupported flag is skipped rather than breaking
24
+ # the build.
25
+ module CpuFlags
26
+ LEVELS = %w[portable tuned max].freeze
27
+
28
+ # Candidate flags per level, in preference order. The first one the compiler
29
+ # accepts wins. `max` degrades march -> mcpu -> mtune; tuned only ever
30
+ # considers -mtune=native (never an instruction-set flag).
31
+ CANDIDATES = {
32
+ 'portable' => [].freeze,
33
+ 'tuned' => ['-mtune=native'].freeze,
34
+ 'max' => ['-march=native', '-mcpu=native', '-mtune=native'].freeze,
35
+ }.freeze
36
+
37
+ # Returns a Hash: { level: String, flags: Array<String>, warning: String|nil }.
38
+ def self.select(raw_level, accepts:)
39
+ level, warning = normalize(raw_level)
40
+ chosen = CANDIDATES[level].find { |flag| accepts.call(flag) }
41
+ { level: level, flags: chosen ? [chosen] : [], warning: warning }
42
+ end
43
+
44
+ # Normalizes the env value to a known level. Unknown values fall back to
45
+ # 'portable' (a typo can then only ever be slower, never non-portable) and
46
+ # return a warning naming the bad value and the fallback.
47
+ def self.normalize(raw_level)
48
+ value = raw_level.to_s.strip.downcase
49
+ return ['portable', nil] if value.empty?
50
+ return [value, nil] if LEVELS.include?(value)
51
+
52
+ ['portable', "SMARTER_JSON_PERFORMANCE=#{raw_level.inspect} is not one of #{LEVELS.join('|')}; using 'portable'."]
53
+ end
54
+ end
55
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "mkmf"
4
4
  require "rbconfig"
5
+ require_relative "cpu_flags"
5
6
 
6
7
  # Ruby sometimes ships CFLAGS with "-g -O3"; drop the debug half so the
7
8
  # extension is built optimized, not with debug info.
@@ -9,11 +10,31 @@ if RbConfig::MAKEFILE_CONFIG["CFLAGS"].include?("-g -O3")
9
10
  RbConfig::MAKEFILE_CONFIG["CFLAGS"] = RbConfig::MAKEFILE_CONFIG["CFLAGS"].sub("-g -O3", "-O3 $(cflags)")
10
11
  end
11
12
 
13
+ # Probe whether the compiler accepts a flag by compiling a trivial program with
14
+ # it. Lets us skip flags the toolchain rejects (e.g. -march=native on Clang/ARM,
15
+ # or GCC-only flags on MSVC) instead of breaking the build. Replaces the old
16
+ # RUBY_PLATFORM string guesses: ask the actual compiler, don't infer from the OS.
17
+ def compiler_accepts?(flag)
18
+ try_compile("int main(void){return 0;}", flag)
19
+ end
20
+
12
21
  optflags = "-O3 -flto -fomit-frame-pointer -DNDEBUG".dup
13
- # -march=native is skipped on arm64-darwin (Apple clang already targets the host).
14
- optflags << " -march=native" unless RUBY_PLATFORM.start_with?("arm64-darwin")
15
- # -fno-semantic-interposition: GCC/Clang only (not MSVC).
16
- optflags << " -fno-semantic-interposition" unless RUBY_PLATFORM.include?("mswin")
22
+
23
+ # CPU optimization level, set via SMARTER_JSON_PERFORMANCE (default: portable).
24
+ # See cpu_flags.rb for the full description of each level.
25
+ #
26
+ # portable (default) - no host-specific flags; runs on any CPU of the same arch.
27
+ # tuned - -mtune=native; host scheduling tuning, still portable.
28
+ # max - host instruction set (-march/-mcpu native); fastest, but
29
+ # NOT portable -- may crash on a CPU lacking those instructions.
30
+ cpu = SmarterJSON::CpuFlags.select(ENV["SMARTER_JSON_PERFORMANCE"], accepts: method(:compiler_accepts?))
31
+ warn(cpu[:warning]) if cpu[:warning]
32
+ cpu[:flags].each { |flag| optflags << " #{flag}" }
33
+ puts("SmarterJSON performance level: #{cpu[:level]} -- optflags: #{optflags}")
34
+
35
+ # -fno-semantic-interposition: GCC/Clang only (not MSVC). Allows intra-library
36
+ # calls to bypass the PLT on Linux and enables more aggressive LTO inlining.
37
+ optflags << " -fno-semantic-interposition" if compiler_accepts?("-fno-semantic-interposition")
17
38
 
18
39
  CONFIG["optflags"] = optflags
19
40
  CONFIG["debugflags"] = ""
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SmarterJSON
4
- VERSION = "1.2.3"
4
+ VERSION = "1.2.4"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smarter_json
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tilo Sloboda
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-06-28 00:00:00.000000000 Z
10
+ date: 2026-07-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bigdecimal
@@ -53,6 +53,7 @@ files:
53
53
  - docs/basic_write_api.md
54
54
  - docs/examples.md
55
55
  - docs/options.md
56
+ - ext/smarter_json/cpu_flags.rb
56
57
  - ext/smarter_json/extconf.rb
57
58
  - ext/smarter_json/smarter_json.c
58
59
  - ext/smarter_json/smarter_json.h
@@ -91,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
92
  - !ruby/object:Gem::Version
92
93
  version: '0'
93
94
  requirements: []
94
- rubygems_version: 3.6.9
95
+ rubygems_version: 4.0.15
95
96
  specification_version: 4
96
97
  summary: A lenient, fast JSON processor for Ruby — reads strict JSON, NDJSON, JSONL,
97
98
  JSON5, HJSON, and the messy JSON humans and LLMs actually write.