uringmachine 0.21.0 → 0.22.1

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +18 -0
  4. data/TODO.md +144 -0
  5. data/benchmark/README.md +139 -0
  6. data/benchmark/bm_io_pipe.rb +70 -0
  7. data/benchmark/bm_io_socketpair.rb +71 -0
  8. data/benchmark/bm_mutex_cpu.rb +57 -0
  9. data/benchmark/bm_mutex_io.rb +64 -0
  10. data/benchmark/bm_pg_client.rb +109 -0
  11. data/benchmark/bm_queue.rb +76 -0
  12. data/benchmark/chart.png +0 -0
  13. data/benchmark/common.rb +139 -0
  14. data/benchmark/dns_client.rb +47 -0
  15. data/{examples/bm_http_parse.rb → benchmark/http_parse.rb} +1 -1
  16. data/benchmark/run_bm.rb +8 -0
  17. data/benchmark/sqlite.rb +108 -0
  18. data/{examples/bm_write.rb → benchmark/write.rb} +4 -4
  19. data/ext/um/um.c +189 -100
  20. data/ext/um/um.h +36 -10
  21. data/ext/um/um_async_op.c +1 -1
  22. data/ext/um/um_class.c +87 -13
  23. data/ext/um/um_const.c +1 -1
  24. data/ext/um/um_op.c +6 -0
  25. data/ext/um/um_sync.c +2 -2
  26. data/ext/um/um_utils.c +16 -0
  27. data/grant-2025/journal.md +118 -1
  28. data/grant-2025/tasks.md +49 -23
  29. data/lib/uringmachine/actor.rb +8 -0
  30. data/lib/uringmachine/dns_resolver.rb +1 -2
  31. data/lib/uringmachine/fiber_scheduler.rb +127 -81
  32. data/lib/uringmachine/version.rb +1 -1
  33. data/lib/uringmachine.rb +32 -3
  34. data/test/helper.rb +7 -18
  35. data/test/test_actor.rb +12 -3
  36. data/test/test_async_op.rb +10 -10
  37. data/test/test_fiber.rb +84 -1
  38. data/test/test_fiber_scheduler.rb +950 -47
  39. data/test/test_um.rb +297 -120
  40. data/uringmachine.gemspec +2 -1
  41. metadata +38 -16
  42. data/examples/bm_fileno.rb +0 -33
  43. data/examples/bm_queue.rb +0 -111
  44. data/examples/bm_side_running.rb +0 -83
  45. data/examples/bm_sqlite.rb +0 -89
  46. data/examples/dns_client.rb +0 -12
  47. /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
  48. /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
  49. /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
  50. /data/{examples/bm_snooze.rb → benchmark/snooze.rb} +0 -0
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './common'
4
+
5
+ GROUPS = 40
6
+ PRODUCERS = 5
7
+ CONSUMERS = 10
8
+ ITEMS = 200000
9
+
10
+ class UMBenchmark
11
+ def do_threads(threads, ios)
12
+ GROUPS.times do
13
+ queue = Queue.new
14
+ PRODUCERS.times do
15
+ threads << Thread.new do
16
+ ITEMS.times { queue << rand(1000) }
17
+ CONSUMERS.times { queue << :stop }
18
+ end
19
+ end
20
+ CONSUMERS.times do
21
+ threads << Thread.new do
22
+ loop do
23
+ item = queue.shift
24
+ break if item == :stop
25
+
26
+ item * rand(1000)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def do_scheduler(scheduler, ios)
34
+ GROUPS.times do
35
+ queue = Queue.new
36
+ PRODUCERS.times do
37
+ Fiber.schedule do
38
+ ITEMS.times { queue << rand(1000) }
39
+ CONSUMERS.times { queue << :stop }
40
+ end
41
+ end
42
+ CONSUMERS.times do
43
+ Fiber.schedule do
44
+ loop do
45
+ item = queue.shift
46
+ break if item == :stop
47
+
48
+ item * rand(1000)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def do_um(machine, fibers, fds)
56
+ GROUPS.times do
57
+ queue = UM::Queue.new
58
+ PRODUCERS.times do
59
+ fibers << machine.spin do
60
+ ITEMS.times { machine.push(queue, rand(1000)) }
61
+ CONSUMERS.times { machine.push(queue, :stop) }
62
+ end
63
+ end
64
+ CONSUMERS.times do
65
+ fibers << machine.spin do
66
+ loop do
67
+ item = machine.shift(queue)
68
+ break if item == :stop
69
+
70
+ item * rand(1000)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
Binary file
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'uringmachine', path: '..'
8
+ gem 'benchmark'
9
+ gem 'io-event'
10
+ gem 'async'
11
+ gem 'pg'
12
+ end
13
+
14
+ require 'uringmachine/fiber_scheduler'
15
+
16
+ class WorkerThreadPool
17
+ def initialize(size)
18
+ @size = size
19
+ @queue = Queue.new
20
+ setup_threads
21
+ sleep 0.01 * @size
22
+ end
23
+
24
+ def queue(&block)
25
+ @queue << block
26
+ end
27
+
28
+ def join
29
+ @size.times { @queue << :stop }
30
+ @threads.each(&:join)
31
+ end
32
+
33
+ def setup_threads
34
+ @threads = @size.times.map {
35
+ Thread.new do
36
+ loop do
37
+ job = @queue.shift
38
+ break if job == :stop
39
+
40
+ job.()
41
+ end
42
+ end
43
+ }
44
+ end
45
+ end
46
+
47
+ class UMBenchmark
48
+ def initialize
49
+ @thread_pool = WorkerThreadPool.new(10)
50
+ end
51
+
52
+ def benchmark
53
+ Benchmark.bm { run_benchmarks(it) }
54
+ end
55
+
56
+ @@benchmarks = {
57
+ threads: [:threads, "Threads"],
58
+ thread_pool: [:thread_pool, "ThreadPool"],
59
+ async_uring: [:scheduler, "Async uring"],
60
+ async_epoll: [:scheduler, "Async epoll"],
61
+ um_fs: [:scheduler, "UM FS"],
62
+ um: [:um, "UM"],
63
+ um_sqpoll: [:um, "UM sqpoll"]
64
+ }
65
+
66
+ def run_benchmarks(b)
67
+ @@benchmarks.each do |sym, (doer, name)|
68
+ b.report(name) { send(:"run_#{sym}") } if respond_to?(:"do_#{doer}")
69
+ end
70
+ end
71
+
72
+ def run_threads
73
+ threads = []
74
+ ios = []
75
+ do_threads(threads, ios)
76
+ threads.each(&:join)
77
+ ios.each { it.close rescue nil }
78
+ end
79
+
80
+ def run_thread_pool
81
+ ios = []
82
+ do_thread_pool(@thread_pool, ios)
83
+ @thread_pool.join
84
+ ios.each { it.close rescue nil }
85
+ end
86
+
87
+ def run_async_uring
88
+ selector ||= IO::Event::Selector::URing.new(Fiber.current)
89
+ scheduler = Async::Scheduler.new(selector:)
90
+ Fiber.set_scheduler(scheduler)
91
+ ios = []
92
+ scheduler.run { do_scheduler(scheduler, ios) }
93
+ ios.each { it.close rescue nil }
94
+ end
95
+
96
+ def run_async_epoll
97
+ selector ||= IO::Event::Selector::EPoll.new(Fiber.current)
98
+ scheduler = Async::Scheduler.new(selector:)
99
+ Fiber.set_scheduler(scheduler)
100
+ ios = []
101
+ scheduler.run { do_scheduler(scheduler, ios) }
102
+ ios.each { it.close rescue nil }
103
+ end
104
+
105
+ def run_um_fs
106
+ machine = UM.new
107
+ scheduler = UM::FiberScheduler.new(machine)
108
+ Fiber.set_scheduler(scheduler)
109
+ ios = []
110
+ do_scheduler(scheduler, ios)
111
+ scheduler.join
112
+ ios.each { it.close rescue nil }
113
+ end
114
+
115
+ def run_um
116
+ machine = UM.new(4096)
117
+ fibers = []
118
+ fds = []
119
+ do_um(machine, fibers, fds)
120
+ machine.await_fibers(fibers)
121
+ puts "UM:"
122
+ p machine.metrics
123
+ fds.each { machine.close(it) }
124
+ end
125
+
126
+ def run_um_sqpoll
127
+ machine = UM.new(4096, true)
128
+ fibers = []
129
+ fds = []
130
+ do_um(machine, fibers, fds)
131
+ machine.await_fibers(fibers)
132
+ fds.each { machine.close_async(it) }
133
+ puts "UM sqpoll:"
134
+ p machine.metrics
135
+ machine.snooze
136
+ end
137
+ end
138
+
139
+ at_exit { UMBenchmark.new.benchmark }
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'uringmachine', path: '..'
8
+ gem 'benchmark'
9
+ gem 'benchmark-ips'
10
+ end
11
+
12
+ require 'uringmachine'
13
+ require 'resolv'
14
+
15
+ def do_addrinfo
16
+ Addrinfo.tcp("status.realiteq.net", 80)
17
+ end
18
+
19
+ def do_resolv
20
+ Resolv.getaddresses('status.realiteq.net')
21
+ end
22
+
23
+ @machine = UM.new
24
+ def do_um
25
+ @machine.resolve('status.realiteq.net')
26
+ end
27
+
28
+ # p do_addrinfo
29
+ # p do_resolv
30
+ # p do_um
31
+ # exit
32
+
33
+ Benchmark.ips do |x|
34
+ x.report("Addrinfo") { do_addrinfo }
35
+ x.report("resolv") { do_resolv }
36
+ x.report("UM.resolve") { do_um }
37
+
38
+ x.compare!(order: :baseline)
39
+ end
40
+
41
+
42
+
43
+ # addrs = machine.resolve('status.realiteq.net')
44
+
45
+ # puts '*' * 40
46
+ # puts addrs.join("\n")
47
+ # puts
@@ -221,4 +221,4 @@ def benchmark
221
221
  end
222
222
 
223
223
  compare_allocs
224
- benchmark
224
+ benchmark
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir["#{__dir__}/bm_*.rb"].each do |fn|
4
+ puts "* #{File.basename(fn)}"
5
+ puts
6
+ puts `ruby -W0 --yjit #{fn}`
7
+ puts
8
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'uringmachine', path: '..'
8
+ gem 'extralite'
9
+ gem 'benchmark'
10
+ gem 'benchmark-ips'
11
+ end
12
+
13
+ require 'uringmachine'
14
+ require 'extralite'
15
+ require 'benchmark/ips'
16
+ require 'securerandom'
17
+ require 'uringmachine/actor'
18
+
19
+ class DBActor2
20
+ def initialize(machine, db)
21
+ @machine = machine
22
+ @db = db
23
+ @mailbox = UM::Queue.new
24
+ end
25
+
26
+ def act
27
+ while (a, k, peer = @machine.shift(@mailbox))
28
+
29
+ begin
30
+ ret = @db.query(*a, **k)
31
+ @machine.schedule(peer, ret)
32
+ rescue => e
33
+ @machine.schedule(peer, e)
34
+ end
35
+ end
36
+ rescue Exception => e
37
+ # handle unhandled exceptions
38
+ ensure
39
+ @machine.fiber_map.delete(self)
40
+ @machine.yield
41
+ end
42
+
43
+ def query(*a, **k)
44
+ @machine.push(@mailbox, [a, k, Fiber.current])
45
+ ret = @machine.yield
46
+ raise(ret) if ret.is_a?(Exception)
47
+ ret
48
+ end
49
+
50
+ def self.spin(machine, db)
51
+ actor = new(machine, db)
52
+ machine.spin { actor.act }
53
+ actor
54
+ end
55
+ end
56
+
57
+ PATH = "/tmp/um_bm_sqlite_#{SecureRandom.hex}"
58
+
59
+ module DBActor
60
+ def setup(db)
61
+ @db = db
62
+ end
63
+
64
+ def query(*, **)
65
+ @db.query(*, **)
66
+ end
67
+ end
68
+
69
+ class Locker
70
+ def initialize(machine, target)
71
+ @machine = machine
72
+ @target = target
73
+ @mutex = UM::Mutex.new
74
+ end
75
+
76
+ def method_missing(sym, *a, **k)
77
+ @machine.synchronize(@mutex) { @target.send(sym, *a, **k) }
78
+ end
79
+ end
80
+
81
+ def prepare_db(machine, path)
82
+ Extralite::Database.new(path).tap {
83
+ it.on_progress(mode: :at_least_once, period: 100, tick: 10) { machine.snooze }
84
+ }
85
+ end
86
+
87
+ machine = UM.new
88
+ mailbox = UM::Queue.new
89
+ raw_db = prepare_db(machine, PATH)
90
+ actor_db = machine.spin_actor(DBActor, prepare_db(machine, PATH))
91
+ actor2_db = DBActor2.spin(machine, prepare_db(machine, PATH))
92
+ locker_db = Locker.new(machine, prepare_db(machine, PATH))
93
+
94
+ p raw_db.query('select 1')
95
+ p actor_db.call(mailbox, :query, 'select 1')
96
+ p actor2_db.query('select 1')
97
+ p locker_db.query('select 1')
98
+
99
+ bm = Benchmark.ips do |x|
100
+ x.config(:time => 5, :warmup => 2)
101
+
102
+ x.report("raw") { raw_db.query('select 1') }
103
+ x.report("actor") { actor_db.call(mailbox, :query, 'select 1') }
104
+ x.report("actor2") { actor2_db.query('select 1') }
105
+ x.report("locker") { locker_db.query('select 1') }
106
+
107
+ x.compare!(order: :baseline)
108
+ end
@@ -11,7 +11,7 @@ end
11
11
  require 'benchmark'
12
12
  require 'uringmachine'
13
13
 
14
- ITERATIONS = 10000
14
+ ITERATIONS = 100000
15
15
  BUF = ('*' * 8192).freeze
16
16
  FN = '/tmp/bm_write'
17
17
 
@@ -50,10 +50,10 @@ end
50
50
  run_io_write(1)
51
51
  run_um_write(1)
52
52
 
53
- Benchmark.bm do |x|
54
- [1, 2, 4, 8].each do |c|
53
+ [1, 2, 4, 8].each do |c|
54
+ Benchmark.bm do |x|
55
55
  x.report("IO (#{c} threads)") { run_io_write(c) }
56
56
  x.report("UM (#{c} fibers) ") { run_um_write(c) }
57
- puts
58
57
  end
58
+ puts
59
59
  end