uringmachine 0.21.0 → 0.22.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +14 -0
  4. data/TODO.md +144 -0
  5. data/benchmark/README.md +173 -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 +135 -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_op.c +6 -0
  24. data/ext/um/um_sync.c +2 -2
  25. data/ext/um/um_utils.c +16 -0
  26. data/grant-2025/journal.md +118 -1
  27. data/grant-2025/tasks.md +48 -22
  28. data/lib/uringmachine/actor.rb +8 -0
  29. data/lib/uringmachine/dns_resolver.rb +1 -2
  30. data/lib/uringmachine/fiber_scheduler.rb +127 -81
  31. data/lib/uringmachine/version.rb +1 -1
  32. data/lib/uringmachine.rb +32 -3
  33. data/test/helper.rb +7 -18
  34. data/test/test_actor.rb +12 -3
  35. data/test/test_async_op.rb +10 -10
  36. data/test/test_fiber.rb +84 -1
  37. data/test/test_fiber_scheduler.rb +950 -47
  38. data/test/test_um.rb +297 -120
  39. data/uringmachine.gemspec +2 -1
  40. metadata +38 -16
  41. data/examples/bm_fileno.rb +0 -33
  42. data/examples/bm_queue.rb +0 -111
  43. data/examples/bm_side_running.rb +0 -83
  44. data/examples/bm_sqlite.rb +0 -89
  45. data/examples/dns_client.rb +0 -12
  46. /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
  47. /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
  48. /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
  49. /data/{examples/bm_snooze.rb → benchmark/snooze.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e14941110d09a575728da68cb038a02334d151f0d384d88ad67a34b21a49d234
4
- data.tar.gz: 158299106b117a973163ae480ea4e9827a0cb88eb76b2323da04fd631e8916ca
3
+ metadata.gz: 74e4816d1191d862df3ba04d46cc038d04b999c22c5604d9a4eec0d1d3fd047c
4
+ data.tar.gz: d857ba559f6c48dfc8d65a1812eb3996c7a65d70d263e016bbb96dbf99e6273c
5
5
  SHA512:
6
- metadata.gz: f71a5b6b6740fea281df8880511b424a0c759e876e17f79a6b661453416d13c2d9d65e0c53ddee5df066ca92472571ffae8c0e9a4ca4fd745d02705a8645965d
7
- data.tar.gz: baa86b9bc0009c69bfe894aae3489d43ed28d1fca44a24a9ddc7cc1625a54de6c17f65731c1a707af561942174f9584554a183a67f8428d5855e799c274db0b6
6
+ metadata.gz: 662c0f7e07df7f87c759eb3e8001aa91c0682d55c63fc46e0429c5ac577de3e0f89476f93b0deb3e05fb3fba4daa4eaae767141615c9e1af1b35df8966f7d988
7
+ data.tar.gz: d22cc49d99ef5772411ebdb8019b6d83eb9e944a83b0a327c638bb5eeaef5661ed3cc34dcb6673a26049e9f7f17cebe76306ca7b847cd4bc0e244c99dfafb210
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ AllCops:
2
+ DisabledByDefault: true
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # 0.22.0 2025-12-10
2
+
3
+ - Fix use of `um_yield` in statx, multishot ops
4
+ - Improve performance of `UM#snooze`
5
+ - Add some profiling info (WIP)
6
+ - Add `UM#metrics` for getting metrics
7
+ - Add `UM#pending_fibers` for detecting leaking fibers in tests
8
+ - More tests and benchmarks
9
+ - Add `UM#await_fibers` for awaiting fibers
10
+ - Add `UM.socketpair` for creating a socket pair
11
+ - Fiber scheduler:
12
+ - Use fiber's mailbox for processing blocking operations
13
+ - Add `#io_close`, `#yield` hooks, remove `#process_fork` hook
14
+
1
15
  # 0.21.0 2025-12-06
2
16
 
3
17
  - Add `UM#submit`
data/TODO.md CHANGED
@@ -1,5 +1,149 @@
1
1
  ## immediate
2
2
 
3
+ ## Measuring CPU time for fibers
4
+
5
+ - use CPU time (CLOCK_THREAD_CPUTIME_ID)
6
+ - measure:
7
+ - time each fiber is waiting
8
+ - time each fiber is running
9
+ - time machine is waiting (for CQEs)
10
+ - time machine is running fibers from the runqueue
11
+ - can be turned on/off at any time
12
+ - no performance impact when off
13
+
14
+ How can this be implemented:
15
+
16
+ - `um_get_time_cpu()` function for reading CPU time (CLOCK_THREAD_CPUTIME_ID) as
17
+ double.
18
+ - add to `struct um`:
19
+
20
+ ```c
21
+ struct um {
22
+ ...
23
+ int profiling_mode;
24
+ double total_time_run;
25
+ double total_time_wait;
26
+ double last_cpu_time;
27
+ }
28
+ ```
29
+
30
+ - `UM#profile=` to turn it on/off.
31
+ - On `machine.profile = true`, reset `total_time_xxx` and `last_cpu_time`
32
+
33
+ ```c
34
+ machine->total_time_run = 0;
35
+ machine->total_time_wait = 0;
36
+ machine->last_cpu_time = um_get_time_cpu();
37
+ ```
38
+
39
+ - when profiling is active:
40
+ - before processing CQEs:
41
+
42
+ ```c
43
+ // before
44
+ double cpu_time0;
45
+ VALUE fiber;
46
+ int profiling_mode = machine->profiling_mode;
47
+ if (profiling_mode) {
48
+ fiber = rb_fiber_current();
49
+ cpu_time0 = um_get_time_cpu();
50
+ double elapsed = cpu_time0 - machine->last_cpu_time;
51
+ um_update_fiber_time_run(fiber, cpu_time0, elapsed);
52
+ machine->total_time_run += elapsed;
53
+ }
54
+ process_cqes(...)
55
+ // after
56
+ if (profiling_mode) {
57
+ double cpu_time1 = um_get_time_cpu();
58
+ double elapsed = cpu_time1 - cpu_time0;
59
+ um_update_fiber_last_time(fiber, cpu_time1);
60
+ machine->total_time_wait += elapsed;
61
+ machine->last_cpu_time = cpu_time1;
62
+ }
63
+ ```
64
+
65
+ - when doing switching, in `um_process_runqueue_op`:
66
+
67
+ ```c
68
+ // before
69
+ double cpu_time;
70
+ VALUE cur_fiber;
71
+ VALUE next_fiber = get_next_fiber(...);
72
+ int profiling_mode = machine->profiling_mode;
73
+ if (profiling_mode) {
74
+ cur_fiber = rb_fiber_current();
75
+ cpu_time = um_get_time_cpu();
76
+ double elapsed = cpu_time - machine->last_cpu_time;
77
+ um_update_fiber_time_run(cur_fiber, cpu_time, elapsed);
78
+ machine->total_time_run += elapsed;
79
+ um_update_fiber_time_wait(next_fiber, cpu_time);
80
+ machine->last_cpu_time = cpu_time;
81
+ }
82
+ do_fiber_transfer(...)
83
+ ```
84
+
85
+ - updating fiber time instance vars:
86
+
87
+ ```c
88
+ inline void um_update_fiber_time_run(VALUE fiber, double stamp, double elapsed) {
89
+ // VALUE fiber_stamp = rb_ivar_get(fiber, ID_time_last_cpu);
90
+ VALUE fiber_total_run = rb_ivar_get(fiber, ID_time_total_run);
91
+ double total = NIL_P(fiber_total_run) ?
92
+ elapsed : NUM2DBL(fiber_total_run) + elapsed;
93
+ rb_ivar_set(fiber, ID_time_total_run, DBL2NUM(total));
94
+ rb_ivar_set(fiber, ID_time_last_cpu, DBL2NUM(stamp));
95
+ }
96
+
97
+ inline void um_update_fiber_time_wait(VALUE fiber, double stamp) {
98
+ VALUE fiber_last_stamp = rb_ivar_get(fiber, ID_time_last_cpu);
99
+ if (likely(!NIL_P(fiber_last_stamp))) {
100
+ double last_stamp = NUM2DBL(fiber_last_stamp);
101
+ double elapsed = stamp - last_stamp;
102
+ VALUE fiber_total_wait = rb_ivar_get(fiber, ID_time_total_wait);
103
+ double total = NIL_P(fiber_total_wait) ?
104
+ elapsed : NUM2DBL(fiber_total_wait) + elapsed;
105
+ rb_ivar_set(fiber, ID_time_total_wait, DBL2NUM(total));
106
+ }
107
+ else
108
+ rb_ivar_set(fiber, ID_time_total_wait, DBL2NUM(0.0));
109
+ rb_ivar_set(fiber, ID_time_last_cpu, DBL2NUM(stamp));
110
+ }
111
+ ```
112
+
113
+ ## Metrics API
114
+
115
+ - machine metrics: `UM#metrics` - returns a hash containing metrics:
116
+
117
+ ```ruby
118
+ {
119
+ size:, # SQ size (entries)
120
+ total_ops:, # total ops submitted
121
+ total_fiber_switches:, # total fiber switches
122
+ total_cqe_waits:, # total number of CQE waits
123
+ ops_pending:, # number of pending ops
124
+ ops_unsubmitted:, # number of unsubmitted
125
+ ops_runqueue:, # number of ops in runqueue
126
+ ops_free:, # number of ops in freelist
127
+ ops_transient:, # number of ops in transient list
128
+ hwm_pending:, # high water mark - pending ops
129
+ hwm_unsubmitted:, # high water mark - unsubmitted ops
130
+ hwm_runqueue:, # high water mark - runqueue depth
131
+ hwm_free:, # high water mark - ops in free list
132
+ hwm_transient:, # high water mark - ops in transient list
133
+ # when profiling is active
134
+ time_total_run:, # total CPU time running
135
+ time_total_wait:, # total CPU time waiting for CQEs
136
+ }
137
+ ```
138
+
139
+ - For this we need to add tracking for:
140
+ - runqueue list size
141
+ - transient list size
142
+ - free list size
143
+ - Those will be done in um_op.c (in linked list management code)
144
+
145
+ - All metrics info in kept in
146
+
3
147
  ## useful concurrency tools
4
148
 
5
149
  - debounce
@@ -0,0 +1,173 @@
1
+ # UringMachine Benchmarks
2
+
3
+ The following benchmarks measure the performance of UringMachine against stock
4
+ Ruby in a variety of scenarios. For each scenario, we compare three different
5
+ implementations:
6
+
7
+ - **Threads**: thread-based concurrency using the stock Ruby I/O and
8
+ synchronization classes.
9
+
10
+ - **Async FS**: fiber-based concurrency with the
11
+ [Async](https://github.com/socketry/async) fiber scheduler, using the stock
12
+ Ruby I/O and synchronization classes.
13
+
14
+ - **UM FS**: fiber-based concurrency with the UringMachine fiber scheduler,
15
+ using the stock Ruby I/O and synchronization classes.
16
+
17
+ - **UM pure**: fiber-based concurrency using the UringMachine low-level (pure)
18
+ API.
19
+
20
+ - **UM sqpoll**: the same as **UM pure** with [submission queue
21
+ polling](https://unixism.net/loti/tutorial/sq_poll.html).
22
+
23
+ <img src="./chart.png">
24
+
25
+ ## Observations:
26
+
27
+ - We see the stark difference between thread-based and fiber-based concurrency.
28
+ For I/O-bound workloads, there's really no contest - and that's exactly why
29
+ the fiber scheduler interface changes everything.
30
+
31
+ - The UringMachine fiber scheduler is in some cases faster than the Async fiber
32
+ scheduler, but not in all. This might be because the Async FS does scheduling
33
+ of fibers in plain Ruby, while the UMFS implements a runqueue in its
34
+ C-extension.
35
+
36
+ - The UringMachine low-level API is faster to use in most cases, and its
37
+ performance advantage grows with the level of concurrency.
38
+
39
+ - SQ polling provides a performance advantage in high-concurrency scenarios,
40
+ depending on the context. It remains to be seen how it affects performance in
41
+ real-world situations.
42
+
43
+ - The [pg](https://github.com/ged/ruby-pg) gem supports the use of fiber
44
+ schedulers, and there too we see a marked performance advantage to using
45
+ fibers instead of threads.
46
+
47
+ ## 1. I/O - Pipe
48
+
49
+ 50 groups, where in each group we create a pipe with a pair of threads/fibers
50
+ writing/reading 1KB of data to the pipe.
51
+
52
+ ```
53
+ C=50x2 user system total real
54
+ Threads 2.501885 3.111840 5.613725 ( 5.017991)
55
+ Async FS 1.189332 0.526275 1.715607 ( 1.715726)
56
+ UM FS 0.715688 0.318851 1.034539 ( 1.034723)
57
+ UM pure 0.241029 0.365079 0.606108 ( 0.606308)
58
+ UM sqpoll 0.217577 0.634414 0.851991 ( 0.593531)
59
+ ```
60
+
61
+ ## 2. I/O - Socketpair
62
+
63
+ 50 concurrent groups, where in each group we create a unix socketpair with a
64
+ pair of threads/fibers writing/reading 1KB of data to the sockets.
65
+
66
+ ```
67
+ N=50 user system total real
68
+ Threads 2.372753 3.612468 5.985221 ( 4.798625)
69
+ Async FS 0.516226 0.877822 1.394048 ( 1.394266)
70
+ UM FS 0.521360 0.875674 1.397034 ( 1.397327)
71
+ UM pure 0.239353 0.642498 0.881851 ( 0.881962)
72
+ UM sqpoll 0.220933 1.021997 1.242930 ( 0.976198)
73
+ ```
74
+
75
+ ## 3. Mutex - CPU-bound
76
+
77
+ 20 concurrent groups, where in each group we create a mutex and start 10 worker
78
+ threads/fibers locking the mutex and performing a Regexp match.
79
+
80
+ ```
81
+ N=20 user system total real
82
+ Threads 5.348378 0.021847 5.370225 ( 5.362117)
83
+ Async FS 5.519970 0.003964 5.523934 ( 5.524536)
84
+ UM FS 5.505282 0.003983 5.509265 ( 5.509840)
85
+ UM pure 5.607048 0.002991 5.610039 ( 5.610749)
86
+ UM sqpoll 5.437836 5.418316 10.856152 ( 5.443331)
87
+ ```
88
+
89
+ ## 4. Mutex - I/O-bound
90
+
91
+ N concurrent groups, where in each group we create a mutex, open a file and
92
+ start 10 worker threads/fibers locking the mutex and writing 1KB chunks to the
93
+ file.
94
+
95
+ ```
96
+ N=1 user system total real
97
+ Threads 0.044103 0.057831 0.101934 ( 0.087204)
98
+ Async FS 0.050608 0.084449 0.135057 ( 0.121300)
99
+ UM FS 0.030355 0.077069 0.107424 ( 0.108146)
100
+ UM pure 0.024489 0.086201 0.110690 ( 0.108023)
101
+ UM sqpoll 0.022752 0.225133 0.247885 ( 0.136251)
102
+
103
+ N=5 user system total real
104
+ Threads 0.214296 0.384078 0.598374 ( 0.467425)
105
+ Async FS 0.085820 0.158782 0.244602 ( 0.139766)
106
+ UM FS 0.064279 0.147278 0.211557 ( 0.117488)
107
+ UM pure 0.036478 0.182950 0.219428 ( 0.119745)
108
+ UM sqpoll 0.036929 0.347573 0.384502 ( 0.160814)
109
+
110
+ N=10 user system total real
111
+ Threads 0.435688 0.752219 1.187907 ( 0.924561)
112
+ Async FS 0.126573 0.303704 0.430277 ( 0.234900)
113
+ UM FS 0.128427 0.215204 0.343631 ( 0.184074)
114
+ UM pure 0.065522 0.359659 0.425181 ( 0.192385)
115
+ UM sqpoll 0.076810 0.477429 0.554239 ( 0.210087)
116
+
117
+ N=20 user system total real
118
+ Threads 0.830763 1.585299 2.416062 ( 1.868194)
119
+ Async FS 0.291823 0.644043 0.935866 ( 0.507887)
120
+ UM FS 0.226202 0.460401 0.686603 ( 0.362879)
121
+ UM pure 0.120524 0.616274 0.736798 ( 0.332182)
122
+ UM sqpoll 0.177150 0.849890 1.027040 ( 0.284069)
123
+
124
+ N=50 user system total real
125
+ Threads 2.124048 4.182537 6.306585 ( 4.878387)
126
+ Async FS 0.897134 1.268629 2.165763 ( 1.254624)
127
+ UM FS 0.733193 0.971821 1.705014 ( 0.933749)
128
+ UM pure 0.226431 1.504441 1.730872 ( 0.760731)
129
+ UM sqpoll 0.557310 2.107389 2.664699 ( 0.783992)
130
+
131
+ N=100 user system total real
132
+ Threads 4.420832 8.628756 13.049588 ( 10.264590)
133
+ Async FS 2.557661 2.532998 5.090659 ( 3.179336)
134
+ UM FS 2.262136 1.912055 4.174191 ( 2.523789)
135
+ UM pure 0.633897 2.793998 3.427895 ( 1.612989)
136
+ UM sqpoll 1.119460 4.193703 5.313163 ( 1.525968)
137
+ ```
138
+
139
+ ## 5. Queue
140
+
141
+ 20 concurrent groups, where in each group we create a queue, start 5 producer
142
+ threads/fibers that push items to the queue, and 10 consumer threads/fibers that
143
+ pull items from the queue.
144
+
145
+ ```
146
+ N=20 user system total real
147
+ Threads 2.522270 0.125569 2.647839 ( 2.638276)
148
+ Async FS 2.245917 0.044860 2.290777 ( 2.291068)
149
+ UM FS 2.235130 0.000958 2.236088 ( 2.236392)
150
+ UM pure 2.125827 0.225050 2.350877 ( 2.351347)
151
+ UM sqpoll 2.044662 2.460344 4.505006 ( 2.261502)
152
+ ```
153
+
154
+ ## 6. Postgres client
155
+
156
+ C concurrent threads/fiber, each thread issuing SELECT query to a PG database.
157
+
158
+ ```
159
+ C=10 user system total real
160
+ Threads 0.813844 0.358261 1.172105 ( 0.987320)
161
+ Async FS 0.545493 0.098608 0.644101 ( 0.644636)
162
+ UM FS 0.523503 0.094336 0.617839 ( 0.619250)
163
+
164
+ C=20 user system total real
165
+ Threads 1.652901 0.714299 2.367200 ( 2.014781)
166
+ Async FS 1.136826 0.212991 1.349817 ( 1.350544)
167
+ UM FS 1.084873 0.205865 1.290738 ( 1.291865)
168
+
169
+ C=50 user system total real
170
+ Threads 4.410604 1.804900 6.215504 ( 5.253016)
171
+ Async FS 2.918522 0.507981 3.426503 ( 3.427966)
172
+ UM FS 2.789549 0.537269 3.326818 ( 3.329802)
173
+ ```
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './common'
4
+
5
+ GROUPS = 50
6
+ ITERATIONS = 10000
7
+
8
+ SIZE = 1024
9
+ DATA = '*' * SIZE
10
+
11
+ class UMBenchmark
12
+ def do_threads(threads, ios)
13
+ GROUPS.times do
14
+ r, w = IO.pipe
15
+ r.sync = true
16
+ w.sync = true
17
+ threads << Thread.new do
18
+ ITERATIONS.times { w.write(DATA) }
19
+ w.close
20
+ end
21
+ threads << Thread.new do
22
+ ITERATIONS.times { r.readpartial(SIZE) }
23
+ r.close
24
+ end
25
+ end
26
+ end
27
+
28
+ def do_thread_pool(thread_pool, ios)
29
+ GROUPS.times do
30
+ r, w = IO.pipe
31
+ r.sync = true
32
+ w.sync = true
33
+ ios << r << w
34
+ ITERATIONS.times {
35
+ thread_pool.queue { w.write(DATA) }
36
+ thread_pool.queue { r.readpartial(SIZE) }
37
+ }
38
+ end
39
+ end
40
+
41
+ def do_scheduler(scheduler, ios)
42
+ GROUPS.times do
43
+ r, w = IO.pipe
44
+ r.sync = true
45
+ w.sync = true
46
+ Fiber.schedule do
47
+ ITERATIONS.times { w.write(DATA) }
48
+ w.close
49
+ end
50
+ Fiber.schedule do
51
+ ITERATIONS.times { r.readpartial(SIZE) }
52
+ r.close
53
+ end
54
+ end
55
+ end
56
+
57
+ def do_um(machine, fibers, fds)
58
+ GROUPS.times do
59
+ r, w = UM.pipe
60
+ fibers << machine.spin do
61
+ ITERATIONS.times { machine.write(w, DATA) }
62
+ machine.close_async(w)
63
+ end
64
+ fibers << machine.spin do
65
+ ITERATIONS.times { machine.read(r, +'', SIZE) }
66
+ machine.close_async(r)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './common'
4
+ require 'socket'
5
+
6
+ GROUPS = 50
7
+ ITERATIONS = 10000
8
+
9
+ SIZE = 1024
10
+ DATA = '*' * SIZE
11
+
12
+ class UMBenchmark
13
+ def do_threads(threads, ios)
14
+ GROUPS.times do
15
+ r, w = Socket.socketpair(:AF_UNIX, :SOCK_STREAM, 0)
16
+ r.sync = true
17
+ w.sync = true
18
+ threads << Thread.new do
19
+ ITERATIONS.times { w.send(DATA, 0) }
20
+ w.close
21
+ end
22
+ threads << Thread.new do
23
+ ITERATIONS.times { r.recv(SIZE) }
24
+ r.close
25
+ end
26
+ end
27
+ end
28
+
29
+ def do_thread_pool(thread_pool, ios)
30
+ GROUPS.times do
31
+ r, w = Socket.socketpair(:AF_UNIX, :SOCK_STREAM, 0)
32
+ r.sync = true
33
+ w.sync = true
34
+ ios << r << w
35
+ ITERATIONS.times {
36
+ thread_pool.queue { w.send(DATA, 0) }
37
+ thread_pool.queue { r.recv(SIZE) }
38
+ }
39
+ end
40
+ end
41
+
42
+ def do_scheduler(scheduler, ios)
43
+ GROUPS.times do
44
+ r, w = Socket.socketpair(:AF_UNIX, :SOCK_STREAM, 0)
45
+ r.sync = true
46
+ w.sync = true
47
+ Fiber.schedule do
48
+ ITERATIONS.times { w.send(DATA, 0) }
49
+ w.close
50
+ end
51
+ Fiber.schedule do
52
+ ITERATIONS.times { r.recv(SIZE) }
53
+ r.close
54
+ end
55
+ end
56
+ end
57
+
58
+ def do_um(machine, fibers, fds)
59
+ GROUPS.times do
60
+ r, w = UM.socketpair(UM::AF_UNIX, UM::SOCK_STREAM, 0)
61
+ fibers << machine.spin do
62
+ ITERATIONS.times { machine.send(w, DATA, SIZE, UM::MSG_WAITALL) }
63
+ machine.close_async(w)
64
+ end
65
+ fibers << machine.spin do
66
+ ITERATIONS.times { machine.recv(r, +'', SIZE, 0) }
67
+ machine.close_async(r)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './common'
4
+
5
+ GROUPS = 20
6
+ WORKERS = 10
7
+ ITERATIONS = 10000
8
+
9
+ STR = "foobar" * 100
10
+ RE = /foo(.+)$/
11
+
12
+ class UMBenchmark
13
+ def do_threads(threads, ios)
14
+ GROUPS.times do
15
+ mutex = Mutex.new
16
+ WORKERS.times do
17
+ threads << Thread.new do
18
+ ITERATIONS.times do
19
+ mutex.synchronize do
20
+ STR.match(RE)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def do_scheduler(scheduler, ios)
29
+ GROUPS.times do
30
+ mutex = Mutex.new
31
+ WORKERS.times do
32
+ Fiber.schedule do
33
+ ITERATIONS.times do
34
+ mutex.synchronize do
35
+ STR.match(RE)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def do_um(machine, fibers, fds)
44
+ GROUPS.times do
45
+ mutex = UM::Mutex.new
46
+ WORKERS.times do
47
+ fibers << machine.spin do
48
+ ITERATIONS.times do
49
+ machine.synchronize(mutex) do
50
+ STR.match(RE)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './common'
4
+ require 'securerandom'
5
+ require 'fileutils'
6
+
7
+ GROUPS = ENV['N']&.to_i || 50
8
+ WORKERS = 10
9
+ ITERATIONS = 1000
10
+
11
+ puts "N=#{GROUPS}"
12
+
13
+ SIZE = 1024
14
+ DATA = "*" * SIZE
15
+
16
+ class UMBenchmark
17
+ def do_threads(threads, ios)
18
+ GROUPS.times do
19
+ mutex = Mutex.new
20
+ ios << (f = File.open("/tmp/mutex_io_threads_#{SecureRandom.hex}", 'w'))
21
+ f.sync = true
22
+ WORKERS.times do
23
+ threads << Thread.new do
24
+ ITERATIONS.times do
25
+ mutex.synchronize do
26
+ f.write(DATA)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def do_scheduler(scheduler, ios)
35
+ GROUPS.times do
36
+ mutex = Mutex.new
37
+ ios << (f = File.open("/tmp/mutex_io_fiber_scheduler_#{SecureRandom.hex}", 'w'))
38
+ f.sync = true
39
+ WORKERS.times do
40
+ Fiber.schedule do
41
+ ITERATIONS.times do
42
+ mutex.synchronize { f.write(DATA) }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def do_um(machine, fibers, fds)
50
+ GROUPS.times do
51
+ mutex = UM::Mutex.new
52
+ fds << (fd = machine.open("/tmp/mutex_io_um_#{SecureRandom.hex}", UM::O_CREAT | UM::O_WRONLY))
53
+ WORKERS.times do
54
+ fibers << machine.spin do
55
+ ITERATIONS.times do
56
+ machine.synchronize(mutex) do
57
+ machine.write(fd, DATA)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end