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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +18 -0
- data/TODO.md +144 -0
- data/benchmark/README.md +139 -0
- data/benchmark/bm_io_pipe.rb +70 -0
- data/benchmark/bm_io_socketpair.rb +71 -0
- data/benchmark/bm_mutex_cpu.rb +57 -0
- data/benchmark/bm_mutex_io.rb +64 -0
- data/benchmark/bm_pg_client.rb +109 -0
- data/benchmark/bm_queue.rb +76 -0
- data/benchmark/chart.png +0 -0
- data/benchmark/common.rb +139 -0
- data/benchmark/dns_client.rb +47 -0
- data/{examples/bm_http_parse.rb → benchmark/http_parse.rb} +1 -1
- data/benchmark/run_bm.rb +8 -0
- data/benchmark/sqlite.rb +108 -0
- data/{examples/bm_write.rb → benchmark/write.rb} +4 -4
- data/ext/um/um.c +189 -100
- data/ext/um/um.h +36 -10
- data/ext/um/um_async_op.c +1 -1
- data/ext/um/um_class.c +87 -13
- data/ext/um/um_const.c +1 -1
- data/ext/um/um_op.c +6 -0
- data/ext/um/um_sync.c +2 -2
- data/ext/um/um_utils.c +16 -0
- data/grant-2025/journal.md +118 -1
- data/grant-2025/tasks.md +49 -23
- data/lib/uringmachine/actor.rb +8 -0
- data/lib/uringmachine/dns_resolver.rb +1 -2
- data/lib/uringmachine/fiber_scheduler.rb +127 -81
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +32 -3
- data/test/helper.rb +7 -18
- data/test/test_actor.rb +12 -3
- data/test/test_async_op.rb +10 -10
- data/test/test_fiber.rb +84 -1
- data/test/test_fiber_scheduler.rb +950 -47
- data/test/test_um.rb +297 -120
- data/uringmachine.gemspec +2 -1
- metadata +38 -16
- data/examples/bm_fileno.rb +0 -33
- data/examples/bm_queue.rb +0 -111
- data/examples/bm_side_running.rb +0 -83
- data/examples/bm_sqlite.rb +0 -89
- data/examples/dns_client.rb +0 -12
- /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
- /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
- /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
- /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
|
data/benchmark/chart.png
ADDED
|
Binary file
|
data/benchmark/common.rb
ADDED
|
@@ -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
|
data/benchmark/run_bm.rb
ADDED
data/benchmark/sqlite.rb
ADDED
|
@@ -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 =
|
|
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
|
-
|
|
54
|
-
|
|
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
|