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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fbf0486feb49686a85ad162dd569ce125b4f168904e6f364e7c4358be234ca78
|
|
4
|
+
data.tar.gz: 27d0e3533c7d41f87b95376258992e37ebfa0d42c32ac0b46e7595939b3f1afa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c4bf87080fe32e145e334944cc1871d109f5b11c5c3cb413c9de3f44cf09b9554ac3fabeb54650c18f67ed96d6381a0290ae8e461ff37b9fef3d27351675ff68
|
|
7
|
+
data.tar.gz: a53addafe3007de3ad77f3e42e4ca3b3f90ae372af24b00beff1b5b486fa4c1f5d6c84de22788192b05328c14c7eeec4e602a77623a00c5c2f8de5bdbd8dca7d
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
# 0.22.1 2025-12-11
|
|
2
|
+
|
|
3
|
+
- Comment out SIGCLD constant
|
|
4
|
+
|
|
5
|
+
# 0.22.0 2025-12-10
|
|
6
|
+
|
|
7
|
+
- Fix use of `um_yield` in statx, multishot ops
|
|
8
|
+
- Improve performance of `UM#snooze`
|
|
9
|
+
- Add some profiling info (WIP)
|
|
10
|
+
- Add `UM#metrics` for getting metrics
|
|
11
|
+
- Add `UM#pending_fibers` for detecting leaking fibers in tests
|
|
12
|
+
- More tests and benchmarks
|
|
13
|
+
- Add `UM#await_fibers` for awaiting fibers
|
|
14
|
+
- Add `UM.socketpair` for creating a socket pair
|
|
15
|
+
- Fiber scheduler:
|
|
16
|
+
- Use fiber's mailbox for processing blocking operations
|
|
17
|
+
- Add `#io_close`, `#yield` hooks, remove `#process_fork` hook
|
|
18
|
+
|
|
1
19
|
# 0.21.0 2025-12-06
|
|
2
20
|
|
|
3
21
|
- 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,139 @@
|
|
|
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
|
+
- `ThreadPool`: thread pool consisting of 10 worker threads, receiving jobs
|
|
11
|
+
through a common queue.
|
|
12
|
+
|
|
13
|
+
- `Async epoll`: fiber-based concurrency with
|
|
14
|
+
[Async](https://github.com/socketry/async) fiber scheduler, using an epoll
|
|
15
|
+
selector.
|
|
16
|
+
|
|
17
|
+
- `Async uring`: fiber-based concurrency with Async fiber scheduler, using a
|
|
18
|
+
uring selector.
|
|
19
|
+
|
|
20
|
+
- `UM FS`: fiber-based concurrency with UringMachine fiber scheduler.
|
|
21
|
+
|
|
22
|
+
- `UM`: fiber-based concurrency using the UringMachine low-level API.
|
|
23
|
+
|
|
24
|
+
<img src="./chart.png">
|
|
25
|
+
|
|
26
|
+
## Observations
|
|
27
|
+
|
|
28
|
+
- We see the stark difference between thread-based and fiber-based concurrency.
|
|
29
|
+
For I/O-bound workloads, there's really no contest - and that's exactly why
|
|
30
|
+
the fiber scheduler interface changes everything.
|
|
31
|
+
|
|
32
|
+
- The UringMachine fiber scheduler is in some cases faster than the Async fiber
|
|
33
|
+
scheduler, but not in all. This might be because the Async FS does scheduling
|
|
34
|
+
of fibers in plain Ruby, while the UMFS implements a runqueue in its
|
|
35
|
+
C-extension.
|
|
36
|
+
|
|
37
|
+
- The UringMachine low-level API is faster to use in most cases, and its
|
|
38
|
+
performance advantage grows with the level of concurrency. Interestingly, when
|
|
39
|
+
performing CPU-bound work, it seems slightly slightly slower. This should be
|
|
40
|
+
investigated.
|
|
41
|
+
|
|
42
|
+
- The [pg](https://github.com/ged/ruby-pg) gem supports the use of fiber
|
|
43
|
+
schedulers, and there too we see a marked performance advantage to using
|
|
44
|
+
fibers instead of threads.
|
|
45
|
+
|
|
46
|
+
According to these benchmarks, for I/O-bound scenarios the different fiber-based
|
|
47
|
+
implementations present a average speedup as follows:
|
|
48
|
+
|
|
49
|
+
|implementation|average factor|
|
|
50
|
+
|--------------|--------------|
|
|
51
|
+
|Async epoll |x2.36 |
|
|
52
|
+
|Async uring |x2.42 |
|
|
53
|
+
|UM FS |x2.85 |
|
|
54
|
+
|UM |x6.20 |
|
|
55
|
+
|
|
56
|
+
## 1. I/O - Pipe
|
|
57
|
+
|
|
58
|
+
50 groups, where in each group we create a pipe with a pair of threads/fibers
|
|
59
|
+
writing/reading 1KB of data to the pipe.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
C=50x2 user system total real
|
|
63
|
+
Threads 2.105002 2.671980 4.776982 ( 4.272842)
|
|
64
|
+
ThreadPool 4.818014 10.740555 15.558569 ( 7.070236)
|
|
65
|
+
Async epoll 1.118937 0.254803 1.373740 ( 1.374298)
|
|
66
|
+
Async uring 1.363248 0.270063 1.633311 ( 1.633696)
|
|
67
|
+
UM FS 0.746332 0.183006 0.929338 ( 0.929619)
|
|
68
|
+
UM 0.237816 0.328352 0.566168 ( 0.566265)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 2. I/O - Socketpair
|
|
72
|
+
|
|
73
|
+
50 concurrent groups, where in each group we create a unix socketpair with a
|
|
74
|
+
pair of threads/fibers writing/reading 1KB of data to the sockets.
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
C=50x2 user system total real
|
|
78
|
+
Threads 2.068122 3.247781 5.315903 ( 4.295488)
|
|
79
|
+
ThreadPool 2.283882 3.461607 5.745489 ( 4.650422)
|
|
80
|
+
Async epoll 0.381400 0.846445 1.227845 ( 1.227983)
|
|
81
|
+
Async uring 0.472526 0.821467 1.293993 ( 1.294166)
|
|
82
|
+
UM FS 0.443023 0.734334 1.177357 ( 1.177576)
|
|
83
|
+
UM 0.116995 0.675997 0.792992 ( 0.793183)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 3. Mutex - CPU-bound
|
|
87
|
+
|
|
88
|
+
20 concurrent groups, where in each group we create a mutex and start 10 worker
|
|
89
|
+
threads/fibers locking the mutex and performing a Regexp match.
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
C=20x10 user system total real
|
|
93
|
+
Threads 5.174998 0.024885 5.199883 ( 5.193211)
|
|
94
|
+
Async epoll 5.309793 0.000949 5.310742 ( 5.311217)
|
|
95
|
+
Async uring 5.341404 0.004860 5.346264 ( 5.346963)
|
|
96
|
+
UM FS 5.363719 0.001976 5.365695 ( 5.366254)
|
|
97
|
+
UM 5.351073 0.005986 5.357059 ( 5.357602)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 4. Mutex - I/O-bound
|
|
101
|
+
|
|
102
|
+
N concurrent groups, where in each group we create a mutex, open a file and
|
|
103
|
+
start 10 worker threads/fibers locking the mutex and writing 1KB chunks to the
|
|
104
|
+
file.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
C=50x10 user system total real
|
|
108
|
+
Threads 2.042649 3.441547 5.484196 ( 4.328783)
|
|
109
|
+
Async epoll 0.810375 0.744084 1.554459 ( 1.554726)
|
|
110
|
+
Async uring 0.854985 1.129260 1.984245 ( 1.140749)
|
|
111
|
+
UM FS 0.686329 0.872376 1.558705 ( 0.845214)
|
|
112
|
+
UM 0.250370 1.323227 1.573597 ( 0.720928)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 5. Postgres client
|
|
116
|
+
|
|
117
|
+
C concurrent threads/fibers, each thread issuing SELECT query to a PG database.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
C=50 user system total real
|
|
121
|
+
Threads 4.304292 1.358116 5.662408 ( 4.795725)
|
|
122
|
+
Async epoll 2.890160 0.432836 3.322996 ( 3.334350)
|
|
123
|
+
Async uring 2.818439 0.433896 3.252335 ( 3.252799)
|
|
124
|
+
UM FS 2.819371 0.443182 3.262553 ( 3.264606)
|
|
125
|
+
```
|
|
126
|
+
## 6. Queue
|
|
127
|
+
|
|
128
|
+
20 concurrent groups, where in each group we create a queue, start 5 producer
|
|
129
|
+
threads/fibers that push items to the queue, and 10 consumer threads/fibers that
|
|
130
|
+
pull items from the queue.
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
C=20x(5+10) user system total real
|
|
134
|
+
Threads 4.880983 0.207451 5.088434 ( 5.071019)
|
|
135
|
+
Async epoll 4.107208 0.006519 4.113727 ( 4.114227)
|
|
136
|
+
Async uring 4.206283 0.028974 4.235257 ( 4.235705)
|
|
137
|
+
UM FS 4.082394 0.001719 4.084113 ( 4.084522)
|
|
138
|
+
UM 4.099893 0.323569 4.423462 ( 4.424089)
|
|
139
|
+
```
|
|
@@ -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
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative './common'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
|
|
6
|
+
C = ENV['C']&.to_i ||50
|
|
7
|
+
I = 1000
|
|
8
|
+
puts "C=#{C}"
|
|
9
|
+
|
|
10
|
+
class UMBenchmark
|
|
11
|
+
CONTAINER_NAME = "pg-#{SecureRandom.hex}"
|
|
12
|
+
|
|
13
|
+
def start_pg_server
|
|
14
|
+
`docker run --name #{CONTAINER_NAME} -e POSTGRES_PASSWORD=my_password -d -p 5432:5432 postgres`
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stop_pg_server
|
|
18
|
+
`docker stop #{CONTAINER_NAME}`
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
PG_OPTS = {
|
|
22
|
+
host: 'localhost',
|
|
23
|
+
user: 'postgres',
|
|
24
|
+
password: 'my_password',
|
|
25
|
+
dbname: 'postgres'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def create_db_conn(retries = 0)
|
|
29
|
+
::PG.connect(PG_OPTS)
|
|
30
|
+
rescue ::PG::ConnectionBad
|
|
31
|
+
if retries < 3
|
|
32
|
+
sleep 0.5
|
|
33
|
+
create_db_conn(retries + 1)
|
|
34
|
+
else
|
|
35
|
+
raise
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
PREPARE_SQL = <<~SQL
|
|
40
|
+
create table if not exists foo(value int, name text);
|
|
41
|
+
create index if not exists idx_foo_value on foo(value);
|
|
42
|
+
with t1 as (
|
|
43
|
+
select generate_series(1,100000)
|
|
44
|
+
),
|
|
45
|
+
t2 as (
|
|
46
|
+
select (random()*1000000)::integer as value,
|
|
47
|
+
(md5(random()::text)) as name
|
|
48
|
+
from t1
|
|
49
|
+
)
|
|
50
|
+
insert into foo(value, name) select * from t2;
|
|
51
|
+
SQL
|
|
52
|
+
|
|
53
|
+
def prepare_db
|
|
54
|
+
STDOUT << "Preparing database..."
|
|
55
|
+
t0 = Time.now
|
|
56
|
+
conn = create_db_conn
|
|
57
|
+
conn.exec(PREPARE_SQL)
|
|
58
|
+
puts " elapsed: #{Time.now - t0}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def query_db(conn)
|
|
62
|
+
min = rand(1000000)
|
|
63
|
+
max = min + 2000
|
|
64
|
+
sql = format(
|
|
65
|
+
'select * from foo where value >= %d and value < %d;', min, max
|
|
66
|
+
)
|
|
67
|
+
conn.query(sql).to_a
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def with_container
|
|
71
|
+
start_pg_server
|
|
72
|
+
sleep 0.5
|
|
73
|
+
prepare_db
|
|
74
|
+
yield
|
|
75
|
+
rescue Exception => e
|
|
76
|
+
p e
|
|
77
|
+
p e.backtrace
|
|
78
|
+
ensure
|
|
79
|
+
stop_pg_server
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def benchmark
|
|
83
|
+
with_container {
|
|
84
|
+
Benchmark.bm { run_benchmarks(it) }
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def do_threads(threads, ios)
|
|
89
|
+
C.times.map do
|
|
90
|
+
threads << Thread.new do
|
|
91
|
+
conn = create_db_conn
|
|
92
|
+
I.times { query_db(conn) }
|
|
93
|
+
ensure
|
|
94
|
+
conn.close
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def do_scheduler(scheduler, ios)
|
|
100
|
+
C.times do
|
|
101
|
+
Fiber.schedule do
|
|
102
|
+
conn = create_db_conn
|
|
103
|
+
I.times { query_db(conn) }
|
|
104
|
+
ensure
|
|
105
|
+
conn.close
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|