atomic-ruby 0.3.2 → 0.5.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 +48 -29
- data/ext/atomic_ruby/atomic_ruby.c +1 -1
- data/lib/atomic-ruby/atomic_boolean.rb +5 -5
- data/lib/atomic-ruby/atomic_count_down_latch.rb +34 -0
- data/lib/atomic-ruby/atomic_ruby.bundle +0 -0
- data/lib/atomic-ruby/atomic_thread_pool.rb +10 -10
- data/lib/atomic-ruby/version.rb +1 -1
- data/lib/atomic-ruby.rb +6 -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: 10b5f273dd24628d2bf10a095cb875647bc922fc36697528c472053c61b2822c
|
|
4
|
+
data.tar.gz: b1b45d921decbc9bd2f927b8b6ed11a8de9e36ca1acacd4dde411953992ffb3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c5ed7e71b51fa5b45e7031c7a08399f0158c588f3fe5b64844f7511fd959ebe7735fcc042d61b84c05729b961a133e7fa12a84663fe2e791b79fe3d02d6319f1
|
|
7
|
+
data.tar.gz: cd538c8c88fa9aa636ae0c0c69063b8288cde1af7b533c958c76096a7ecede7967ab45159ca04d010d507e084b8b13c38aa225d2ba675b146d2c7e8c473d147f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2025-07-17
|
|
4
|
+
|
|
5
|
+
- Add shortcut aliases for `AtomicRuby` namespaced classes
|
|
6
|
+
|
|
7
|
+
## [0.4.0] - 2025-07-06
|
|
8
|
+
|
|
9
|
+
- Revert "Fix `AtomicThreadPool#<<` shutdown check race condition"
|
|
10
|
+
- Add `:name` to `AtomicThreadPool` initializer
|
|
11
|
+
- Add `AtomicCountDownLatch`
|
|
12
|
+
|
|
3
13
|
## [0.3.2] - 2025-06-14
|
|
4
14
|
|
|
5
15
|
- Fix `AtomicThreadPool#<<` shutdown check race condition
|
data/README.md
CHANGED
|
@@ -21,12 +21,12 @@ gem install atomic-ruby
|
|
|
21
21
|
|
|
22
22
|
## Usage
|
|
23
23
|
|
|
24
|
-
`
|
|
24
|
+
`Atom`:
|
|
25
25
|
|
|
26
26
|
```ruby
|
|
27
27
|
require "atomic-ruby"
|
|
28
28
|
|
|
29
|
-
atom =
|
|
29
|
+
atom = Atom.new(0)
|
|
30
30
|
p atom.value #=> 0
|
|
31
31
|
atom.swap { |current_value| current_value + 1 }
|
|
32
32
|
p atom.value #=> 1
|
|
@@ -34,12 +34,12 @@ atom.swap { |current_value| current_value + 1 }
|
|
|
34
34
|
p atom.value #=> 2
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
`
|
|
37
|
+
`AtomicBoolean`:
|
|
38
38
|
|
|
39
39
|
```ruby
|
|
40
40
|
require "atomic-ruby"
|
|
41
41
|
|
|
42
|
-
atom =
|
|
42
|
+
atom = AtomicBoolean.new(false)
|
|
43
43
|
p atom.value #=> false
|
|
44
44
|
p atom.false? #=> true
|
|
45
45
|
p atom.true? #=> false
|
|
@@ -49,14 +49,14 @@ atom.toggle
|
|
|
49
49
|
p atom.false? #=> true
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
`
|
|
52
|
+
`AtomicThreadPool`:
|
|
53
53
|
|
|
54
54
|
```ruby
|
|
55
55
|
require "atomic-ruby"
|
|
56
56
|
|
|
57
57
|
results = []
|
|
58
58
|
|
|
59
|
-
pool =
|
|
59
|
+
pool = AtomicThreadPool.new(size: 4)
|
|
60
60
|
p pool.length #=> 4
|
|
61
61
|
|
|
62
62
|
10.times do |idx|
|
|
@@ -78,11 +78,30 @@ p results #=> [8, 7, 10, 9, 6, 5, 3, 4, 2, 1]
|
|
|
78
78
|
p results.sort #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
`AtomicCountDownLatch`:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
require "atomic-ruby"
|
|
85
|
+
|
|
86
|
+
latch = AtomicCountDownLatch.new(3)
|
|
87
|
+
p latch.count #=> 3
|
|
88
|
+
|
|
89
|
+
threads = 3.times.map do
|
|
90
|
+
Thread.new do
|
|
91
|
+
sleep(rand(5))
|
|
92
|
+
latch.count_down
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
latch.wait
|
|
97
|
+
p latch.count #=> 0
|
|
98
|
+
```
|
|
99
|
+
|
|
81
100
|
## Benchmarks
|
|
82
101
|
|
|
83
102
|
<details>
|
|
84
103
|
|
|
85
|
-
<summary>
|
|
104
|
+
<summary>Atom</summary>
|
|
86
105
|
|
|
87
106
|
<br>
|
|
88
107
|
|
|
@@ -128,7 +147,7 @@ end
|
|
|
128
147
|
|
|
129
148
|
class AtomicRubyAtomicBankAccount
|
|
130
149
|
def initialize(balance)
|
|
131
|
-
@balance =
|
|
150
|
+
@balance = Atom.new(balance)
|
|
132
151
|
end
|
|
133
152
|
|
|
134
153
|
def balance
|
|
@@ -190,7 +209,7 @@ puts "Atomic Ruby Atomic Bank Account: #{results[2].real.round(6)} seconds"
|
|
|
190
209
|
|
|
191
210
|
ruby version: ruby 3.4.4 (2025-05-14 revision a38531fd3f) +YJIT +PRISM [arm64-darwin24]
|
|
192
211
|
concurrent-ruby version: 1.3.5
|
|
193
|
-
atomic-ruby version: 0.
|
|
212
|
+
atomic-ruby version: 0.4.0
|
|
194
213
|
|
|
195
214
|
Balances:
|
|
196
215
|
Synchronized Bank Account Balance: 975
|
|
@@ -198,16 +217,16 @@ Concurrent Ruby Atomic Bank Account Balance: 975
|
|
|
198
217
|
Atomic Ruby Atomic Bank Account Balance: 975
|
|
199
218
|
|
|
200
219
|
Benchmark Results:
|
|
201
|
-
Synchronized Bank Account: 5.
|
|
202
|
-
Concurrent Ruby Atomic Bank Account: 5.
|
|
203
|
-
Atomic Ruby Atomic Bank Account: 5.
|
|
220
|
+
Synchronized Bank Account: 5.110062 seconds
|
|
221
|
+
Concurrent Ruby Atomic Bank Account: 5.107966 seconds
|
|
222
|
+
Atomic Ruby Atomic Bank Account: 5.107739 seconds
|
|
204
223
|
```
|
|
205
224
|
|
|
206
225
|
</details>
|
|
207
226
|
|
|
208
227
|
<details>
|
|
209
228
|
|
|
210
|
-
<summary>
|
|
229
|
+
<summary>AtomicBoolean</summary>
|
|
211
230
|
|
|
212
231
|
```ruby
|
|
213
232
|
# frozen_string_literal: true
|
|
@@ -262,7 +281,7 @@ Benchmark.ips do |x|
|
|
|
262
281
|
end
|
|
263
282
|
|
|
264
283
|
x.report("Atomic Ruby Atomic Boolean Toggle") do
|
|
265
|
-
boolean =
|
|
284
|
+
boolean = AtomicBoolean.new(false)
|
|
266
285
|
20.times.map do
|
|
267
286
|
Thread.new do
|
|
268
287
|
100.times do
|
|
@@ -281,34 +300,34 @@ end
|
|
|
281
300
|
|
|
282
301
|
ruby version: ruby 3.4.4 (2025-05-14 revision a38531fd3f) +YJIT +PRISM [arm64-darwin24]
|
|
283
302
|
concurrent-ruby version: 1.3.5
|
|
284
|
-
atomic-ruby version: 0.
|
|
303
|
+
atomic-ruby version: 0.4.0
|
|
285
304
|
|
|
286
305
|
Warming up --------------------------------------
|
|
287
306
|
Synchronized Boolean Toggle
|
|
288
|
-
|
|
307
|
+
102.000 i/100ms
|
|
289
308
|
Concurrent Ruby Atomic Boolean Toggle
|
|
290
|
-
|
|
309
|
+
88.000 i/100ms
|
|
291
310
|
Atomic Ruby Atomic Boolean Toggle
|
|
292
|
-
|
|
311
|
+
109.000 i/100ms
|
|
293
312
|
Calculating -------------------------------------
|
|
294
313
|
Synchronized Boolean Toggle
|
|
295
|
-
|
|
314
|
+
1.062k (± 2.5%) i/s (941.81 μs/i) - 5.406k in 5.094827s
|
|
296
315
|
Concurrent Ruby Atomic Boolean Toggle
|
|
297
|
-
|
|
316
|
+
981.495 (± 3.5%) i/s (1.02 ms/i) - 4.928k in 5.027167s
|
|
298
317
|
Atomic Ruby Atomic Boolean Toggle
|
|
299
|
-
|
|
318
|
+
1.274k (± 1.5%) i/s (784.70 μs/i) - 6.431k in 5.047458s
|
|
300
319
|
|
|
301
320
|
Comparison:
|
|
302
|
-
Atomic Ruby Atomic Boolean Toggle:
|
|
303
|
-
Synchronized Boolean Toggle:
|
|
304
|
-
Concurrent Ruby Atomic Boolean Toggle:
|
|
321
|
+
Atomic Ruby Atomic Boolean Toggle: 1274.4 i/s
|
|
322
|
+
Synchronized Boolean Toggle: 1061.8 i/s - 1.20x slower
|
|
323
|
+
Concurrent Ruby Atomic Boolean Toggle: 981.5 i/s - 1.30x slower
|
|
305
324
|
```
|
|
306
325
|
|
|
307
326
|
</details>
|
|
308
327
|
|
|
309
328
|
<details>
|
|
310
329
|
|
|
311
|
-
<summary>
|
|
330
|
+
<summary>AtomicThreadPool</summary>
|
|
312
331
|
|
|
313
332
|
<br>
|
|
314
333
|
|
|
@@ -325,7 +344,7 @@ results = []
|
|
|
325
344
|
result = Benchmark.measure do
|
|
326
345
|
pool = case idx
|
|
327
346
|
when 0 then Concurrent::FixedThreadPool.new(20)
|
|
328
|
-
when 1 then
|
|
347
|
+
when 1 then AtomicThreadPool.new(size: 20)
|
|
329
348
|
end
|
|
330
349
|
|
|
331
350
|
100.times do
|
|
@@ -359,11 +378,11 @@ puts "Atomic Ruby Atomic Thread Pool: #{results[1].real.round(6)} seconds"
|
|
|
359
378
|
|
|
360
379
|
ruby version: ruby 3.4.4 (2025-05-14 revision a38531fd3f) +YJIT +PRISM [arm64-darwin24]
|
|
361
380
|
concurrent-ruby version: 1.3.5
|
|
362
|
-
atomic-ruby version: 0.
|
|
381
|
+
atomic-ruby version: 0.4.0
|
|
363
382
|
|
|
364
383
|
Benchmark Results:
|
|
365
|
-
Concurrent Ruby Thread Pool: 5.
|
|
366
|
-
Atomic Ruby Atomic Thread Pool: 4.
|
|
384
|
+
Concurrent Ruby Thread Pool: 5.02207 seconds
|
|
385
|
+
Atomic Ruby Atomic Thread Pool: 4.503302 seconds
|
|
367
386
|
```
|
|
368
387
|
|
|
369
388
|
</details>
|
|
@@ -24,7 +24,7 @@ static void atomic_ruby_atom_compact(void *ptr) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
static const rb_data_type_t atomic_ruby_atom_type = {
|
|
27
|
-
.wrap_struct_name = "
|
|
27
|
+
.wrap_struct_name = "Atom",
|
|
28
28
|
.function = {
|
|
29
29
|
.dmark = atomic_ruby_atom_mark,
|
|
30
30
|
.dfree = atomic_ruby_atom_free,
|
|
@@ -11,11 +11,11 @@ module AtomicRuby
|
|
|
11
11
|
raise InvalidBooleanError, "expected boolean to be a `TrueClass` or `FalseClass`, got #{value.class}"
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
@
|
|
14
|
+
@boolean = Atom.new(value)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def value
|
|
18
|
-
@
|
|
18
|
+
@boolean.value
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def true?
|
|
@@ -27,15 +27,15 @@ module AtomicRuby
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def make_true
|
|
30
|
-
@
|
|
30
|
+
@boolean.swap { true }
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def make_false
|
|
34
|
-
@
|
|
34
|
+
@boolean.swap { false }
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def toggle
|
|
38
|
-
@
|
|
38
|
+
@boolean.swap { |current_value| !current_value }
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "atom"
|
|
4
|
+
|
|
5
|
+
module AtomicRuby
|
|
6
|
+
class AtomicCountDownLatch
|
|
7
|
+
class InvalidCountError < StandardError; end
|
|
8
|
+
class AlreadyCountedDownError < StandardError; end
|
|
9
|
+
|
|
10
|
+
def initialize(count)
|
|
11
|
+
unless count.is_a?(Integer)
|
|
12
|
+
raise InvalidCountError, "expected count to be an `Integer`, got #{count.class}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
@count = Atom.new(count)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def count
|
|
19
|
+
@count.value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def count_down
|
|
23
|
+
unless @count.value > 0
|
|
24
|
+
raise AlreadyCountedDownError, "count has already reached zero"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@count.swap { |current_value| current_value - 1 }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def wait
|
|
31
|
+
Thread.pass while @count.value > 0
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
Binary file
|
|
@@ -8,8 +8,9 @@ module AtomicRuby
|
|
|
8
8
|
class UnsupportedWorkTypeError < StandardError; end
|
|
9
9
|
class InvalidWorkQueueingError < StandardError; end
|
|
10
10
|
|
|
11
|
-
def initialize(size:)
|
|
11
|
+
def initialize(size:, name: nil)
|
|
12
12
|
@size = size
|
|
13
|
+
@name = name
|
|
13
14
|
@queue = Atom.new([])
|
|
14
15
|
@threads = []
|
|
15
16
|
@started_threads = Atom.new(0)
|
|
@@ -23,13 +24,11 @@ module AtomicRuby
|
|
|
23
24
|
raise UnsupportedWorkTypeError, "expected work to be a `Proc`, got #{work.class}"
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
@
|
|
27
|
-
|
|
28
|
-
raise InvalidWorkQueueingError, "cannot queue work during or after pool shutdown"
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
current_queue += [work]
|
|
27
|
+
if @stopping.true?
|
|
28
|
+
raise InvalidWorkQueueingError, "cannot queue work during or after pool shutdown"
|
|
32
29
|
end
|
|
30
|
+
|
|
31
|
+
@queue.swap { |current_queue| current_queue += [work] }
|
|
33
32
|
true
|
|
34
33
|
end
|
|
35
34
|
|
|
@@ -52,8 +51,9 @@ module AtomicRuby
|
|
|
52
51
|
def start
|
|
53
52
|
@threads = @size.times.map do |num|
|
|
54
53
|
Thread.new(num) do |idx|
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
thread_name = String.new("AtomicThreadPool thread #{idx}")
|
|
55
|
+
thread_name << " for #{@name}" if @name
|
|
56
|
+
Thread.current.name = thread_name
|
|
57
57
|
|
|
58
58
|
@started_threads.swap { |current_count| current_count + 1 }
|
|
59
59
|
|
|
@@ -65,7 +65,7 @@ module AtomicRuby
|
|
|
65
65
|
begin
|
|
66
66
|
work.call
|
|
67
67
|
rescue => err
|
|
68
|
-
puts "#{
|
|
68
|
+
puts "#{thread_name} rescued:"
|
|
69
69
|
puts "#{err.class}: #{err.message}"
|
|
70
70
|
puts err.backtrace.join("\n")
|
|
71
71
|
end
|
data/lib/atomic-ruby/version.rb
CHANGED
data/lib/atomic-ruby.rb
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
require_relative "atomic-ruby/version"
|
|
4
4
|
require_relative "atomic-ruby/atomic_ruby"
|
|
5
5
|
require_relative "atomic-ruby/atom"
|
|
6
|
+
require_relative "atomic-ruby/atomic_boolean"
|
|
6
7
|
require_relative "atomic-ruby/atomic_thread_pool"
|
|
8
|
+
require_relative "atomic-ruby/atomic_count_down_latch"
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
Atom = AtomicRuby::Atom
|
|
11
|
+
AtomicBoolean = AtomicRuby::AtomicBoolean
|
|
12
|
+
AtomicThreadPool = AtomicRuby::AtomicThreadPool
|
|
13
|
+
AtomicCountDownLatch = AtomicRuby::AtomicCountDownLatch
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: atomic-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Young
|
|
@@ -27,6 +27,7 @@ files:
|
|
|
27
27
|
- lib/atomic-ruby.rb
|
|
28
28
|
- lib/atomic-ruby/atom.rb
|
|
29
29
|
- lib/atomic-ruby/atomic_boolean.rb
|
|
30
|
+
- lib/atomic-ruby/atomic_count_down_latch.rb
|
|
30
31
|
- lib/atomic-ruby/atomic_ruby.bundle
|
|
31
32
|
- lib/atomic-ruby/atomic_thread_pool.rb
|
|
32
33
|
- lib/atomic-ruby/version.rb
|
|
@@ -50,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
50
51
|
- !ruby/object:Gem::Version
|
|
51
52
|
version: '0'
|
|
52
53
|
requirements: []
|
|
53
|
-
rubygems_version: 3.
|
|
54
|
+
rubygems_version: 3.6.9
|
|
54
55
|
specification_version: 4
|
|
55
56
|
summary: Atomic primitives for Ruby
|
|
56
57
|
test_files: []
|