atomic-ruby 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +22 -42
- data/ext/atomic_ruby/atomic_ruby.c +49 -12
- data/ext/atomic_ruby/atomic_ruby.h +1 -1
- data/ext/atomic_ruby/extconf.rb +0 -3
- data/lib/atomic-ruby/atom.rb +5 -6
- data/lib/atomic-ruby/atomic_thread_pool.rb +2 -0
- data/lib/atomic-ruby/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bef30ee19f89bf213e837f5ee565b4e0b2f926c422dd7204270f349a331142c
|
|
4
|
+
data.tar.gz: b19ac4ac6c8f363c891aae5c7ef4b4c39c80b420673f71890fe8c4f1974dee89
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8afd84e8e6cc724949c88c6485bd51e8d3ba86e56852ad742136613ce2f4a36468de88b7c011a2061218ff7edb189a8e8a13f3049741d5a020862c8cc9760e7d
|
|
7
|
+
data.tar.gz: d5727d75e5b3c6f4cce6c7dd394b1fd7d64d48f3940124a27f2725e1947c6aac375604fb98364a24aa6bbcb50670680b3b755c566f5092829b21eff7bef98d10
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.9.0] - 2025-11-05
|
|
4
|
+
|
|
5
|
+
- Switch `AtomicThreadPool` back to atomics now that Ractor safety is lazy
|
|
6
|
+
- Don't enforce Ractor safety unless crossing Ractor boundaries
|
|
7
|
+
- Add `AtomicThreadPool#size` and `AtomicThreadPool#queue_size` aliases
|
|
8
|
+
|
|
9
|
+
## [0.8.1] - 2025-11-01
|
|
10
|
+
|
|
11
|
+
- Don't require `AtomicThreadPool#<<` to be given a shareable proc
|
|
12
|
+
|
|
3
13
|
## [0.8.0] - 2025-11-01
|
|
4
14
|
|
|
5
15
|
- Fix Ractor safety (requires Ruby 3.5+)
|
data/README.md
CHANGED
|
@@ -99,18 +99,6 @@ p latch.count #=> 0
|
|
|
99
99
|
|
|
100
100
|
> [!NOTE]
|
|
101
101
|
> `Atom`, `AtomicBoolean`, and `AtomicCountDownLatch` are Ractor-safe in Ruby 3.5+.
|
|
102
|
-
>
|
|
103
|
-
> When storing procs in atoms (including queueing them with `AtomicThreadPool#<<`), create them
|
|
104
|
-
> using `Ractor.shareable_proc`, as `Ractor.make_shareable` cannot convert regular procs to
|
|
105
|
-
> shareable ones when the proc's context is not shareable.
|
|
106
|
-
>
|
|
107
|
-
> ```ruby
|
|
108
|
-
> # This will raise an error in Ruby 3.5+ (proc created in non-shareable context)
|
|
109
|
-
> atom = Atom.new(proc { puts "hello" })
|
|
110
|
-
>
|
|
111
|
-
> # Use this instead
|
|
112
|
-
> atom = Atom.new(Ractor.shareable_proc { puts "hello" })
|
|
113
|
-
> ```
|
|
114
102
|
|
|
115
103
|
## Benchmarks
|
|
116
104
|
|
|
@@ -222,9 +210,9 @@ puts "Atomic Ruby Atomic Bank Account: #{results[2].real.round(6)} seconds"
|
|
|
222
210
|
```
|
|
223
211
|
> bundle exec rake compile && bundle exec ruby examples/atom_benchmark.rb
|
|
224
212
|
|
|
225
|
-
ruby version: ruby 3.5.0dev (2025-
|
|
213
|
+
ruby version: ruby 3.5.0dev (2025-11-05T10:35:48Z master 946d2d036f) +YJIT +PRISM [arm64-darwin25]
|
|
226
214
|
concurrent-ruby version: 1.3.5
|
|
227
|
-
atomic-ruby version: 0.
|
|
215
|
+
atomic-ruby version: 0.9.0
|
|
228
216
|
|
|
229
217
|
Balances:
|
|
230
218
|
Synchronized Bank Account Balance: 975
|
|
@@ -232,9 +220,9 @@ Concurrent Ruby Atomic Bank Account Balance: 975
|
|
|
232
220
|
Atomic Ruby Atomic Bank Account Balance: 975
|
|
233
221
|
|
|
234
222
|
Benchmark Results:
|
|
235
|
-
Synchronized Bank Account: 5.
|
|
236
|
-
Concurrent Ruby Atomic Bank Account: 5.
|
|
237
|
-
Atomic Ruby Atomic Bank Account: 5.
|
|
223
|
+
Synchronized Bank Account: 5.104234 seconds
|
|
224
|
+
Concurrent Ruby Atomic Bank Account: 5.113334 seconds
|
|
225
|
+
Atomic Ruby Atomic Bank Account: 5.097197 seconds
|
|
238
226
|
```
|
|
239
227
|
|
|
240
228
|
</details>
|
|
@@ -313,29 +301,29 @@ end
|
|
|
313
301
|
```
|
|
314
302
|
> bundle exec rake compile && bundle exec ruby examples/atomic_boolean_benchmark.rb
|
|
315
303
|
|
|
316
|
-
ruby version: ruby 3.5.0dev (2025-
|
|
304
|
+
ruby version: ruby 3.5.0dev (2025-11-05T10:35:48Z master 946d2d036f) +YJIT +PRISM [arm64-darwin25]
|
|
317
305
|
concurrent-ruby version: 1.3.5
|
|
318
|
-
atomic-ruby version: 0.
|
|
306
|
+
atomic-ruby version: 0.9.0
|
|
319
307
|
|
|
320
308
|
Warming up --------------------------------------
|
|
321
309
|
Synchronized Boolean Toggle
|
|
322
|
-
|
|
310
|
+
158.000 i/100ms
|
|
323
311
|
Concurrent Ruby Atomic Boolean Toggle
|
|
324
|
-
|
|
312
|
+
113.000 i/100ms
|
|
325
313
|
Atomic Ruby Atomic Boolean Toggle
|
|
326
|
-
|
|
314
|
+
122.000 i/100ms
|
|
327
315
|
Calculating -------------------------------------
|
|
328
316
|
Synchronized Boolean Toggle
|
|
329
|
-
1.
|
|
317
|
+
1.521k (± 2.1%) i/s (657.49 μs/i) - 7.742k in 5.092579s
|
|
330
318
|
Concurrent Ruby Atomic Boolean Toggle
|
|
331
|
-
1.
|
|
319
|
+
1.141k (± 1.6%) i/s (876.12 μs/i) - 5.763k in 5.050298s
|
|
332
320
|
Atomic Ruby Atomic Boolean Toggle
|
|
333
|
-
1.
|
|
321
|
+
1.243k (± 1.3%) i/s (804.64 μs/i) - 6.222k in 5.007246s
|
|
334
322
|
|
|
335
323
|
Comparison:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
Concurrent Ruby Atomic Boolean Toggle:
|
|
324
|
+
Synchronized Boolean Toggle: 1520.9 i/s
|
|
325
|
+
Atomic Ruby Atomic Boolean Toggle: 1242.8 i/s - 1.22x slower
|
|
326
|
+
Concurrent Ruby Atomic Boolean Toggle: 1141.4 i/s - 1.33x slower
|
|
339
327
|
```
|
|
340
328
|
|
|
341
329
|
</details>
|
|
@@ -353,14 +341,6 @@ require "benchmark"
|
|
|
353
341
|
require "concurrent-ruby"
|
|
354
342
|
require_relative "../lib/atomic-ruby"
|
|
355
343
|
|
|
356
|
-
def shareable_proc(&block)
|
|
357
|
-
if AtomicRuby::RACTOR_SAFE
|
|
358
|
-
Ractor.shareable_proc(&block)
|
|
359
|
-
else
|
|
360
|
-
block
|
|
361
|
-
end
|
|
362
|
-
end
|
|
363
|
-
|
|
364
344
|
results = []
|
|
365
345
|
|
|
366
346
|
2.times do |idx|
|
|
@@ -371,11 +351,11 @@ results = []
|
|
|
371
351
|
end
|
|
372
352
|
|
|
373
353
|
100.times do
|
|
374
|
-
pool <<
|
|
354
|
+
pool << proc { sleep(0.2) }
|
|
375
355
|
end
|
|
376
356
|
|
|
377
357
|
100.times do
|
|
378
|
-
pool <<
|
|
358
|
+
pool << proc { 1_000_000.times.map(&:itself).sum }
|
|
379
359
|
end
|
|
380
360
|
|
|
381
361
|
pool.shutdown
|
|
@@ -399,13 +379,13 @@ puts "Atomic Ruby Atomic Thread Pool: #{results[1].real.round(6)} seconds"
|
|
|
399
379
|
```
|
|
400
380
|
> bundle exec rake compile && bundle exec ruby examples/atomic_thread_pool_benchmark.rb
|
|
401
381
|
|
|
402
|
-
ruby version: ruby 3.5.0dev (2025-
|
|
382
|
+
ruby version: ruby 3.5.0dev (2025-11-05T10:35:48Z master 946d2d036f) +YJIT +PRISM [arm64-darwin25]
|
|
403
383
|
concurrent-ruby version: 1.3.5
|
|
404
|
-
atomic-ruby version: 0.
|
|
384
|
+
atomic-ruby version: 0.9.0
|
|
405
385
|
|
|
406
386
|
Benchmark Results:
|
|
407
|
-
Concurrent Ruby Thread Pool: 5.
|
|
408
|
-
Atomic Ruby Atomic Thread Pool: 4.
|
|
387
|
+
Concurrent Ruby Thread Pool: 5.169928 seconds
|
|
388
|
+
Atomic Ruby Atomic Thread Pool: 4.831942 seconds
|
|
409
389
|
```
|
|
410
390
|
|
|
411
391
|
</details>
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
typedef struct {
|
|
4
4
|
volatile VALUE value;
|
|
5
|
+
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
6
|
+
VALUE initialized_ractor;
|
|
7
|
+
#endif
|
|
5
8
|
} atomic_ruby_atom_t;
|
|
6
9
|
|
|
7
10
|
static void atomic_ruby_atom_mark(void *ptr) {
|
|
8
11
|
atomic_ruby_atom_t *atomic_ruby_atom = (atomic_ruby_atom_t *)ptr;
|
|
9
12
|
rb_gc_mark_movable(atomic_ruby_atom->value);
|
|
13
|
+
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
14
|
+
rb_gc_mark_movable(atomic_ruby_atom->initialized_ractor);
|
|
15
|
+
#endif
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
static void atomic_ruby_atom_free(void *ptr) {
|
|
@@ -21,6 +27,9 @@ static size_t atomic_ruby_atom_memsize(const void *ptr) {
|
|
|
21
27
|
static void atomic_ruby_atom_compact(void *ptr) {
|
|
22
28
|
atomic_ruby_atom_t *atomic_ruby_atom = (atomic_ruby_atom_t *)ptr;
|
|
23
29
|
atomic_ruby_atom->value = rb_gc_location(atomic_ruby_atom->value);
|
|
30
|
+
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
31
|
+
atomic_ruby_atom->initialized_ractor = rb_gc_location(atomic_ruby_atom->initialized_ractor);
|
|
32
|
+
#endif
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
static const rb_data_type_t atomic_ruby_atom_type = {
|
|
@@ -38,35 +47,54 @@ static const rb_data_type_t atomic_ruby_atom_type = {
|
|
|
38
47
|
#endif
|
|
39
48
|
};
|
|
40
49
|
|
|
50
|
+
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
51
|
+
static void ensure_value_shareable(VALUE self, atomic_ruby_atom_t *atom, VALUE value) {
|
|
52
|
+
bool check_shareable = NIL_P(atom->initialized_ractor);
|
|
53
|
+
|
|
54
|
+
if (!check_shareable) {
|
|
55
|
+
VALUE current_ractor = rb_funcall(rb_cRactor, rb_intern("current"), 0);
|
|
56
|
+
if (current_ractor != atom->initialized_ractor) {
|
|
57
|
+
check_shareable = true;
|
|
58
|
+
RB_OBJ_WRITE(self, &atom->initialized_ractor, Qnil);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (check_shareable && !rb_ractor_shareable_p(value)) {
|
|
63
|
+
rb_raise(rb_eArgError, "value must be a shareable object when used across ractors");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
#endif
|
|
67
|
+
|
|
41
68
|
static VALUE rb_cAtom_allocate(VALUE klass) {
|
|
42
69
|
atomic_ruby_atom_t *atomic_ruby_atom;
|
|
43
70
|
VALUE obj = TypedData_Make_Struct(klass, atomic_ruby_atom_t, &atomic_ruby_atom_type, atomic_ruby_atom);
|
|
44
71
|
RB_OBJ_WRITE(obj, &atomic_ruby_atom->value, Qnil);
|
|
45
|
-
return obj;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
72
|
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
rb_raise(rb_eArgError, "value must be a shareable object");
|
|
52
|
-
}
|
|
53
|
-
}
|
|
73
|
+
VALUE current_ractor = rb_funcall(rb_cRactor, rb_intern("current"), 0);
|
|
74
|
+
RB_OBJ_WRITE(obj, &atomic_ruby_atom->initialized_ractor, current_ractor);
|
|
54
75
|
#endif
|
|
76
|
+
return obj;
|
|
77
|
+
}
|
|
55
78
|
|
|
56
79
|
static VALUE rb_cAtom_initialize(VALUE self, VALUE value) {
|
|
57
80
|
atomic_ruby_atom_t *atomic_ruby_atom;
|
|
58
81
|
TypedData_Get_Struct(self, atomic_ruby_atom_t, &atomic_ruby_atom_type, atomic_ruby_atom);
|
|
82
|
+
RB_OBJ_WRITE(self, &atomic_ruby_atom->value, value);
|
|
59
83
|
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
60
|
-
|
|
84
|
+
rb_obj_freeze(self);
|
|
85
|
+
FL_SET_RAW(self, RUBY_FL_SHAREABLE);
|
|
61
86
|
#endif
|
|
62
|
-
RB_OBJ_WRITE(self, &atomic_ruby_atom->value, value);
|
|
63
87
|
return self;
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
static VALUE rb_cAtom_value(VALUE self) {
|
|
67
91
|
atomic_ruby_atom_t *atomic_ruby_atom;
|
|
68
92
|
TypedData_Get_Struct(self, atomic_ruby_atom_t, &atomic_ruby_atom_type, atomic_ruby_atom);
|
|
69
|
-
|
|
93
|
+
VALUE value = (VALUE)RUBY_ATOMIC_PTR_LOAD(atomic_ruby_atom->value);
|
|
94
|
+
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
95
|
+
ensure_value_shareable(self, atomic_ruby_atom, value);
|
|
96
|
+
#endif
|
|
97
|
+
return value;
|
|
70
98
|
}
|
|
71
99
|
|
|
72
100
|
static VALUE rb_cAtom_swap(VALUE self) {
|
|
@@ -78,7 +106,7 @@ static VALUE rb_cAtom_swap(VALUE self) {
|
|
|
78
106
|
expected_old_value = atomic_ruby_atom->value;
|
|
79
107
|
new_value = rb_yield(expected_old_value);
|
|
80
108
|
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
81
|
-
|
|
109
|
+
ensure_value_shareable(self, atomic_ruby_atom, new_value);
|
|
82
110
|
#endif
|
|
83
111
|
} while (RUBY_ATOMIC_VALUE_CAS(atomic_ruby_atom->value, expected_old_value, new_value) != expected_old_value);
|
|
84
112
|
RB_OBJ_WRITTEN(self, expected_old_value, new_value);
|
|
@@ -86,6 +114,14 @@ static VALUE rb_cAtom_swap(VALUE self) {
|
|
|
86
114
|
return new_value;
|
|
87
115
|
}
|
|
88
116
|
|
|
117
|
+
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
118
|
+
static VALUE rb_cAtom_initialized_ractor(VALUE self) {
|
|
119
|
+
atomic_ruby_atom_t *atomic_ruby_atom;
|
|
120
|
+
TypedData_Get_Struct(self, atomic_ruby_atom_t, &atomic_ruby_atom_type, atomic_ruby_atom);
|
|
121
|
+
return atomic_ruby_atom->initialized_ractor;
|
|
122
|
+
}
|
|
123
|
+
#endif
|
|
124
|
+
|
|
89
125
|
RUBY_FUNC_EXPORTED void Init_atomic_ruby(void) {
|
|
90
126
|
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
91
127
|
rb_ext_ractor_safe(true);
|
|
@@ -100,6 +136,7 @@ RUBY_FUNC_EXPORTED void Init_atomic_ruby(void) {
|
|
|
100
136
|
rb_define_method(rb_cAtom, "_swap", rb_cAtom_swap, 0);
|
|
101
137
|
|
|
102
138
|
#ifdef ATOMIC_RUBY_RACTOR_SAFE
|
|
139
|
+
rb_define_method(rb_cAtom, "_initialized_ractor", rb_cAtom_initialized_ractor, 0);
|
|
103
140
|
rb_define_const(rb_mAtomicRuby, "RACTOR_SAFE", Qtrue);
|
|
104
141
|
#else
|
|
105
142
|
rb_define_const(rb_mAtomicRuby, "RACTOR_SAFE", Qfalse);
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
#include "ruby.h"
|
|
5
5
|
#include "ruby/atomic.h"
|
|
6
|
-
#include "ruby/ractor.h"
|
|
7
6
|
#include "ruby/version.h"
|
|
8
7
|
|
|
9
8
|
#if RUBY_API_VERSION_CODE >= 30500
|
|
10
9
|
#define ATOMIC_RUBY_RACTOR_SAFE 1
|
|
10
|
+
#include "ruby/ractor.h"
|
|
11
11
|
#endif
|
|
12
12
|
|
|
13
13
|
#endif /* ATOMIC_RUBY_ATOM_H */
|
data/ext/atomic_ruby/extconf.rb
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "mkmf"
|
|
4
4
|
|
|
5
|
-
# Makes all symbols private by default to avoid unintended conflict
|
|
6
|
-
# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED
|
|
7
|
-
# selectively, or entirely remove this flag.
|
|
8
5
|
append_cflags("-fvisibility=hidden")
|
|
9
6
|
|
|
10
7
|
create_makefile("atomic_ruby/atomic_ruby")
|
data/lib/atomic-ruby/atom.rb
CHANGED
|
@@ -5,9 +5,7 @@ require "atomic_ruby/atomic_ruby"
|
|
|
5
5
|
module AtomicRuby
|
|
6
6
|
class Atom
|
|
7
7
|
def initialize(value)
|
|
8
|
-
_initialize(
|
|
9
|
-
|
|
10
|
-
freeze if RACTOR_SAFE
|
|
8
|
+
_initialize(value)
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
def value
|
|
@@ -16,14 +14,15 @@ module AtomicRuby
|
|
|
16
14
|
|
|
17
15
|
def swap(&block)
|
|
18
16
|
_swap do |old_value|
|
|
19
|
-
|
|
17
|
+
make_shareable_if_needed(block.call(old_value))
|
|
20
18
|
end
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
private
|
|
24
22
|
|
|
25
|
-
def
|
|
26
|
-
if RACTOR_SAFE
|
|
23
|
+
def make_shareable_if_needed(value)
|
|
24
|
+
if RACTOR_SAFE &&
|
|
25
|
+
(_initialized_ractor.nil? || Ractor.current != _initialized_ractor)
|
|
27
26
|
Ractor.make_shareable(value)
|
|
28
27
|
else
|
|
29
28
|
value
|
data/lib/atomic-ruby/version.rb
CHANGED