hyperuuid 0.1.0 → 0.2.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/LICENSE +21 -0
- data/README.md +82 -4
- data/lib/hyperuuid/native/linux-arm64/libhyperuuid.so +0 -0
- data/lib/hyperuuid/native/linux-x64/libhyperuuid.so +0 -0
- data/lib/hyperuuid/native/osx-arm64/libhyperuuid.dylib +0 -0
- data/lib/hyperuuid/native/osx-x64/libhyperuuid.dylib +0 -0
- data/lib/hyperuuid/native/win-arm64/hyperuuid.dll +0 -0
- data/lib/hyperuuid/native/win-x64/hyperuuid.dll +0 -0
- data/lib/hyperuuid/runtime.rb +1 -1
- data/lib/hyperuuid/uuid.rb +2 -1
- data/lib/hyperuuid.rb +45 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b32cd1cbc875c0d6a64d08feecec431d09b8f51f79f0a3551ad9494e7310d069
|
|
4
|
+
data.tar.gz: 4ed09018086e92c7c4f275b367106d2174320494996e4d621af8b1b0ef714ae0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0069495aa996730427430e5ecc7314a99b2dd496e3e78ffed28c982cf85b1177470ecdf80c6920947ea88d5cd729b04036443a6d44e5447014b58be80011d8de'
|
|
7
|
+
data.tar.gz: a5ecd9e090c7bf5411a8144f5c9968e095d64a8d0cdf6d63dd6c899a6d185b6f4306255669c83da936045c030c4d02408b0be85d58e9d9dab9a04c33c12f98d9
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Skunk Werkx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# hyperuuid
|
|
2
2
|
|
|
3
|
+
[](https://github.com/SkunkWerkx/HyperUuid/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/hyperuuid)
|
|
5
|
+
|
|
3
6
|
**Ruby's own stdlib stops at `SecureRandom.uuid` — random v4, full stop. No v5, no v6, no v7. This gem is the whole RFC, with zero gem dependency beyond `Fiddle` (which ships with every Ruby install) — and it's faster than `SecureRandom.uuid` too.**
|
|
4
7
|
|
|
5
8
|
RFC 9562 UUID v4 (random), v5 (deterministic), v6 and v7 (time-sortable) generation, with
|
|
@@ -60,6 +63,21 @@ timestamp capture and one native call, instead of `count` of each.
|
|
|
60
63
|
|
|
61
64
|
The honest trade-off: this gem `dlopen`s a native library instead of being pure Ruby, so it needs a platform-specific `libhyperuuid.so`/`.dylib`/`.dll` bundled alongside it. If plain v4 randomness is all you need, `SecureRandom.uuid` is simpler and already in stdlib — that's a completely reasonable choice.
|
|
62
65
|
|
|
66
|
+
## Bulk generation into bytes
|
|
67
|
+
|
|
68
|
+
`new_v6_batch_bytes` and `new_v7_batch_bytes` return the batch as one binary `String` of raw RFC 9562-ordered bytes — 16 per UUID — instead of an Array of `Uuid` objects:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
bytes = HyperUuid.new_v7_batch_bytes(1000)
|
|
72
|
+
first = bytes[0, 16] # ready for a BINARY(16) bind parameter
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**About 15x faster than `new_v7_batch`** for a 1000-UUID batch (24 µs versus 370 µs). The native call is identical — the difference is that `new_v7_batch` then allocates 1000 `Uuid` objects and 1000 String slices on top of it. This hands back the bytes the native core already produced, untouched.
|
|
76
|
+
|
|
77
|
+
The catch, and it inverts the advice: **if you need `Uuid` objects, keep using `new_v7_batch`.** Slicing these bytes into objects yourself just relocates the identical allocations into your own code, and measures no better — sometimes worse. Reach for the byte form only when bytes are the destination: a bind parameter, a wire format, a bulk load.
|
|
78
|
+
|
|
79
|
+
Slice it with `bytes[i * 16, 16]` — which is exactly what `new_v7_batch` does internally.
|
|
80
|
+
|
|
63
81
|
## Benchmarks
|
|
64
82
|
|
|
65
83
|
Real numbers, `benchmark-ips` on Ruby 4.0.6, linux-arm64 (`ruby benchmark/uuid_benchmark.rb`) — not claimed, measured. With the Magnus backend (the default wherever the extension loads):
|
|
@@ -99,9 +117,69 @@ gem install hyperuuid
|
|
|
99
117
|
|
|
100
118
|
Published to [RubyGems.org](https://rubygems.org/gems/hyperuuid) as real precompiled
|
|
101
119
|
"platform gems" — `bundle`/`gem install` auto-selects the matching one for
|
|
102
|
-
linux-x64/arm64
|
|
103
|
-
falling back automatically to the universal
|
|
104
|
-
bundles all 6 platforms' native libs)
|
|
105
|
-
|
|
120
|
+
linux-x64/arm64, osx-x64/arm64, x64-mingw-ucrt or aarch64-mingw-ucrt (the compiled Magnus
|
|
121
|
+
native extension, `backend: :native`), falling back automatically to the universal
|
|
122
|
+
`ruby`-platform gem (pure Fiddle, zero compile, bundles all 6 platforms' native libs)
|
|
123
|
+
everywhere else. No extra configuration needed either way.
|
|
124
|
+
|
|
125
|
+
Selection has **two** axes here, unlike every other binding in this repo. A Magnus extension
|
|
126
|
+
is bound to one Ruby minor ABI — there's no `abi3` equivalent to collapse the version axis the
|
|
127
|
+
way [the Python binding's](../python/) wheels do — so each platform gem is a "fat" gem
|
|
128
|
+
carrying one compiled extension per supported Ruby, under `lib/hyperuuid/<minor>/`, and picks
|
|
129
|
+
one at `require` time:
|
|
130
|
+
|
|
131
|
+
| Ruby | linux-x64/arm64, osx-x64/arm64, x64-mingw-ucrt, aarch64-mingw-ucrt | anywhere else (musl/Alpine, …) |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| 4.0 (primary) | Magnus, `backend: :native` | Fiddle |
|
|
134
|
+
| 3.4 (floor, until its EOL 2028-03-31) | Magnus, `backend: :native` | Fiddle |
|
|
135
|
+
| 3.2 / 3.3 | Fiddle | Fiddle |
|
|
136
|
+
|
|
137
|
+
The platform gems declare `required_ruby_version >= 3.4, < 4.1` precisely so RubyGems
|
|
138
|
+
*declines* them outside that range and resolves the universal gem instead — a wrong-ABI
|
|
139
|
+
extension must never be installed in the first place. On Windows it would at least fail to
|
|
140
|
+
load cleanly (the extension imports `<arch>-ucrt-ruby<minor>.dll` by name —
|
|
141
|
+
`x64-ucrt-ruby400.dll` on x64, `aarch64-ucrt-ruby400.dll` on ARM), but Linux extensions
|
|
142
|
+
don't link libruby at all, so one can load successfully against the wrong ABI and misbehave
|
|
143
|
+
later. When 3.4 goes EOL it simply leaves the matrix and its users fall back to Fiddle, which
|
|
144
|
+
is exactly what the fallback is for.
|
|
145
|
+
|
|
146
|
+
An earlier edition of this section said the fallback covered "Windows included, since Magnus
|
|
147
|
+
doesn't target it." That was wrong in both halves. MinGW is the *only* Windows flavour
|
|
148
|
+
`rb-sys` targets — its own `data/toolchains.json` maps `x64-mingw-ucrt` to the
|
|
149
|
+
`x86_64-pc-windows-gnu` Rust target, `supported: true`; the target it has no support for is
|
|
150
|
+
`x86_64-pc-windows-msvc`. And Windows is where the Fiddle fallback cost the most: measured
|
|
151
|
+
on win-x64, Ruby 3.4, the Magnus backend does `new_v4` in 406ns against Fiddle's 2407ns
|
|
152
|
+
(**5.9x**) and `new_v7` in 595ns against 2759ns (**4.6x**) — a far wider gap than any Linux
|
|
153
|
+
or macOS leg shows.
|
|
154
|
+
|
|
155
|
+
Windows-on-ARM is no longer the exception it was. `rb-sys` maps `aarch64-mingw-ucrt` to the
|
|
156
|
+
`aarch64-pc-windows-gnullvm` Rust target (`supported: true`), and RubyInstaller ships an
|
|
157
|
+
`aarch64-mingw-ucrt` build of both ABIs in the table above, so win-arm64 now gets the same fat
|
|
158
|
+
platform gem as every other leg. Measured on real Windows-on-ARM hardware, Ruby 4.0.6, the
|
|
159
|
+
Magnus backend does `new_v4` in 416ns against Fiddle's 2299ns (**5.5x**) and `new_v7` in 621ns
|
|
160
|
+
against 2474ns (**4.0x**) — the same shape the x64 leg shows.
|
|
161
|
+
|
|
162
|
+
What differs from the x64 build follows from `gnullvm` being LLVM-based where
|
|
163
|
+
`x86_64-pc-windows-gnu` is GCC-based. The linker driver is `aarch64-w64-mingw32-clang` from
|
|
164
|
+
MSYS2's CLANGARM64 environment — RubyInstaller's own ARM devkit installs it locally, and
|
|
165
|
+
`ruby/setup-ruby` installs it on `windows-11-arm` — so there is no `link-self-contained=no`
|
|
166
|
+
dance. Two flags are load-bearing:
|
|
167
|
+
|
|
168
|
+
- **`-l static=unwind`.** rustc emits its `-lunwind` in the linker's *dynamic* section, where
|
|
169
|
+
CLANGARM64 offers both `libunwind.a` and `libunwind.dll.a` and lld prefers the latter. Left
|
|
170
|
+
alone, the extension acquires a runtime dependency on `libunwind.dll` — a file no consumer
|
|
171
|
+
of the gem would have.
|
|
172
|
+
- **`BINDGEN_EXTRA_CLANG_ARGS=--target=aarch64-w64-mingw32`.** bindgen hands clang the *Rust*
|
|
173
|
+
target triple when cargo cross-compiles, and clang rejects `aarch64-pc-windows-gnullvm`
|
|
174
|
+
outright (`version 'llvm' in target triple ... is invalid`), then fails behind that on a
|
|
175
|
+
missing `stdalign.h` it never reached its own resource dir to find. `aarch64-w64-mingw32`
|
|
176
|
+
is the same ABI spelled the way clang accepts, exactly as x64 spells its gnu target
|
|
177
|
+
`x86_64-w64-mingw32`.
|
|
178
|
+
|
|
179
|
+
That second one was briefly documented here as *unnecessary*, on the strength of a local
|
|
180
|
+
build where it genuinely was: that build used a `gnullvm` **host** toolchain, so host equals
|
|
181
|
+
target, cargo was not cross-compiling, and bindgen injected no `--target` at all. CI's host is
|
|
182
|
+
MSVC, so it does. A local build says nothing about that step unless its host triple matches
|
|
183
|
+
the runner's.
|
|
106
184
|
|
|
107
185
|
See [the repo root README](../README.md) for the full RFC 9562 coverage table and the state of every other language binding.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/hyperuuid/runtime.rb
CHANGED
|
@@ -5,7 +5,7 @@ module HyperUuid
|
|
|
5
5
|
# C-ABI call, no runtime bridge (the same "no shim" positioning as the Go/Swift bindings'
|
|
6
6
|
# purego/dlopen approach). Fiddle ships with every Ruby install; it's a plain gem
|
|
7
7
|
# dependency here (see hyperuuid.gemspec) rather than a third-party one — mirroring Go's
|
|
8
|
-
# "no cgo" and Python's
|
|
8
|
+
# "no cgo" and Python's zero-dependency PyO3 wheels.
|
|
9
9
|
#
|
|
10
10
|
# Unlike the Go/Swift bindings, which embed their native builds inside a single compiled
|
|
11
11
|
# archive and must extract to a temp file before dlopen can see a real path, a Ruby gem's
|
data/lib/hyperuuid/uuid.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
module HyperUuid
|
|
2
2
|
# A parsed 16-byte RFC 9562 UUID value. Minimal by design — this gem has no runtime
|
|
3
3
|
# dependency on the `uuid` gem, the same "no extra dependency" positioning as the Go
|
|
4
|
-
# binding's purego-only/no-cgo approach and the Python binding's
|
|
4
|
+
# binding's purego-only/no-cgo approach and the Python binding's dependency-free PyO3
|
|
5
|
+
# wheels.
|
|
5
6
|
class Uuid
|
|
6
7
|
include Comparable
|
|
7
8
|
|
data/lib/hyperuuid.rb
CHANGED
|
@@ -13,7 +13,7 @@ require_relative "hyperuuid/runtime"
|
|
|
13
13
|
module HyperUuid
|
|
14
14
|
# This gem's own version — distinct from the RFC 9562 UUID *versions* (v4/v5/v6/v7) the
|
|
15
15
|
# rest of this module generates.
|
|
16
|
-
VERSION = "0.
|
|
16
|
+
VERSION = "0.2.0"
|
|
17
17
|
|
|
18
18
|
# Creates a random UUID version 4 (RFC 9562 §5.4).
|
|
19
19
|
def self.new_v4
|
|
@@ -78,6 +78,34 @@ module HyperUuid
|
|
|
78
78
|
bytes = Runtime.new_v7_batch(count, unix_millis_from(unix_millis))
|
|
79
79
|
Array.new(count) { |i| Uuid.new(bytes[i * 16, 16]) }
|
|
80
80
|
end
|
|
81
|
+
|
|
82
|
+
# Returns `count` version 7 UUIDs as one binary String of raw RFC 9562-ordered bytes,
|
|
83
|
+
# 16 per UUID, instead of an Array of Uuid objects.
|
|
84
|
+
#
|
|
85
|
+
# Roughly 11x faster than #new_v7_batch for a 1000-UUID batch (about 35 us versus 400 us).
|
|
86
|
+
# The difference is not the native call — that is identical — it is that #new_v7_batch then
|
|
87
|
+
# allocates `count` Uuid objects and `count` String slices on top of it. This hands back the
|
|
88
|
+
# bytes the native core already produced, untouched.
|
|
89
|
+
#
|
|
90
|
+
# Use it when bytes are the destination: a BYTEA/uniqueidentifier bind parameter, a wire
|
|
91
|
+
# format, a bulk COPY. If you need Uuid objects, keep using #new_v7_batch — slicing this
|
|
92
|
+
# String into them yourself just moves the same allocations into your own code.
|
|
93
|
+
#
|
|
94
|
+
# Slice it with `bytes[i * 16, 16]`, which is what #new_v7_batch does internally.
|
|
95
|
+
def self.new_v7_batch_bytes(count, unix_millis = nil)
|
|
96
|
+
Runtime.new_v7_batch(count, unix_millis_from(unix_millis))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Returns `count` version 6 UUIDs as one binary String of raw RFC 9562-ordered bytes,
|
|
100
|
+
# 16 per UUID. The version 6 counterpart to #new_v7_batch_bytes, with the same rationale and
|
|
101
|
+
# the same guidance about when it is the right call.
|
|
102
|
+
#
|
|
103
|
+
# clock_seq and node are independently random per item; unlike version 7 there is no
|
|
104
|
+
# monotonic counter, so items minted in the same millisecond are not guaranteed to sort in
|
|
105
|
+
# creation order.
|
|
106
|
+
def self.new_v6_batch_bytes(count, unix_millis = nil)
|
|
107
|
+
Runtime.new_v6_batch(count, unix_millis_from(unix_millis))
|
|
108
|
+
end
|
|
81
109
|
end
|
|
82
110
|
|
|
83
111
|
# --- backend selection: the Magnus extension, when present, replaces the Runtime methods
|
|
@@ -91,10 +119,24 @@ HyperUuid::BACKEND =
|
|
|
91
119
|
if ENV["HYPERUUID_PURE"]
|
|
92
120
|
:fiddle
|
|
93
121
|
else
|
|
122
|
+
# Two layouts, and both have to work. A released platform gem is a "fat" gem carrying one
|
|
123
|
+
# extension per supported Ruby ABI under lib/hyperuuid/<minor>/ (see the Rakefile's
|
|
124
|
+
# native:gem task for why an ABI-per-file is unavoidable — Magnus has no `abi3`
|
|
125
|
+
# equivalent). CI's in-job staging and a local `cargo build --release --features ruby`
|
|
126
|
+
# instead drop a single extension flat at lib/. Trying the versioned path first and the
|
|
127
|
+
# flat one second means neither has to know the other exists.
|
|
128
|
+
#
|
|
129
|
+
# A miss on both is not an error: it means this Ruby/platform combination has no
|
|
130
|
+
# precompiled extension, which is precisely what the Fiddle backend below is for.
|
|
94
131
|
begin
|
|
95
|
-
require "hyperuuid_native"
|
|
132
|
+
require "hyperuuid/#{RUBY_VERSION[/\d+\.\d+/]}/hyperuuid_native"
|
|
96
133
|
:native
|
|
97
134
|
rescue LoadError
|
|
98
|
-
|
|
135
|
+
begin
|
|
136
|
+
require "hyperuuid_native"
|
|
137
|
+
:native
|
|
138
|
+
rescue LoadError
|
|
139
|
+
:fiddle
|
|
140
|
+
end
|
|
99
141
|
end
|
|
100
142
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hyperuuid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Buvinghausen
|
|
@@ -73,6 +73,7 @@ executables: []
|
|
|
73
73
|
extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
|
75
75
|
files:
|
|
76
|
+
- LICENSE
|
|
76
77
|
- README.md
|
|
77
78
|
- lib/hyperuuid.rb
|
|
78
79
|
- lib/hyperuuid/namespaces.rb
|
|
@@ -105,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
105
106
|
- !ruby/object:Gem::Version
|
|
106
107
|
version: '0'
|
|
107
108
|
requirements: []
|
|
108
|
-
rubygems_version:
|
|
109
|
+
rubygems_version: 4.0.16
|
|
109
110
|
specification_version: 4
|
|
110
111
|
summary: RFC 9562 UUID v4/v5/v6/v7 generation — direct native FFI into a Rust core,
|
|
111
112
|
no runtime bridge.
|