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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +14 -0
- data/TODO.md +144 -0
- data/benchmark/README.md +173 -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 +135 -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_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 +48 -22
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 74e4816d1191d862df3ba04d46cc038d04b999c22c5604d9a4eec0d1d3fd047c
|
|
4
|
+
data.tar.gz: d857ba559f6c48dfc8d65a1812eb3996c7a65d70d263e016bbb96dbf99e6273c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 662c0f7e07df7f87c759eb3e8001aa91c0682d55c63fc46e0429c5ac577de3e0f89476f93b0deb3e05fb3fba4daa4eaae767141615c9e1af1b35df8966f7d988
|
|
7
|
+
data.tar.gz: d22cc49d99ef5772411ebdb8019b6d83eb9e944a83b0a327c638bb5eeaef5661ed3cc34dcb6673a26049e9f7f17cebe76306ca7b847cd4bc0e244c99dfafb210
|
data/.rubocop.yml
ADDED
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
|
data/benchmark/README.md
ADDED
|
@@ -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
|