uringmachine 0.29.2 → 0.31.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/CHANGELOG.md +18 -4
- data/README.md +46 -38
- data/TODO.md +68 -75
- data/benchmark/bm_io_ssl.rb +128 -0
- data/benchmark/bm_redis_client.rb +76 -0
- data/benchmark/common.rb +1 -0
- data/benchmark/gets.rb +7 -7
- data/benchmark/gets_concurrent.rb +10 -10
- data/benchmark/http_parse.rb +15 -15
- data/benchmark/http_server_accept_queue.rb +11 -7
- data/benchmark/http_server_multi_accept.rb +7 -7
- data/benchmark/http_server_multi_ractor.rb +7 -7
- data/benchmark/http_server_single_thread.rb +7 -7
- data/benchmark/openssl.rb +50 -22
- data/docs/design/buffer_pool.md +1 -1
- data/examples/fiber_concurrency_io.rb +52 -0
- data/examples/fiber_concurrency_naive.rb +26 -0
- data/examples/fiber_concurrency_runqueue.rb +33 -0
- data/examples/io_uring_simple.c +24 -0
- data/examples/pg.rb +2 -2
- data/examples/stream.rb +2 -2
- data/ext/um/um.c +71 -3
- data/ext/um/um.h +34 -22
- data/ext/um/um_buffer_pool.c +11 -11
- data/ext/um/um_class.c +57 -0
- data/ext/um/um_connection.c +775 -0
- data/ext/um/um_connection_class.c +394 -0
- data/ext/um/um_ssl.c +43 -7
- data/ext/um/um_utils.c +1 -1
- data/grant-2025/final-report.md +269 -0
- data/grant-2025/journal.md +1 -1
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +47 -6
- data/test/{test_stream.rb → test_connection.rb} +321 -151
- data/test/test_ssl.rb +27 -0
- data/test/test_um.rb +174 -17
- metadata +13 -6
- data/ext/um/um_stream.c +0 -674
- data/ext/um/um_stream_class.c +0 -303
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9c6af55ee77291dcf17471dde7dd9d68b731fd9a8dea4d1dccdcc09b325b9163
|
|
4
|
+
data.tar.gz: 516624d1a86287bbd0978d5aaf0cca540063ca7f6aa4cf2bab8cb9f23214d32d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9d845525499342080228b44afb5cabb84af6bb0cb14081cc5e4e2eb627ab638312c0df818f5d109ab99d6e34a299bb21da23711f131f4a9641d688cee5cf35e
|
|
7
|
+
data.tar.gz: d67d15b398bac118b85219be3669a1479eba68e5d93ceb2e3f598238ae2fbefd7cf83dcffa132fe12ed0b0385d675c15e7f89a05150290da6fbc4d8bc40a1431
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# 0.31.0 2026-03-31
|
|
2
|
+
|
|
3
|
+
- Rework `Stream` into `Connection` class:
|
|
4
|
+
- Rename modes, improve SSL detection
|
|
5
|
+
- Rename and enhance different read methods
|
|
6
|
+
- Add `#write` method
|
|
7
|
+
- Add `#resp_write` method
|
|
8
|
+
|
|
9
|
+
# 0.30.0 2026-03-23
|
|
10
|
+
|
|
11
|
+
- Add `Stream#each`
|
|
12
|
+
- Add `UM#splice`, `UM#tee`, `UM#fsync`
|
|
13
|
+
- Add `UM#tcp_listen`, `UM#tcp_connect`
|
|
14
|
+
|
|
1
15
|
# 0.29.2 2026-03-15
|
|
2
16
|
|
|
3
17
|
- Add `Stream#consumed`, `Stream#pending`
|
|
@@ -24,7 +38,7 @@
|
|
|
24
38
|
|
|
25
39
|
# 0.28.2 2026-02-20
|
|
26
40
|
|
|
27
|
-
- Fix `Stream#
|
|
41
|
+
- Fix `Stream#read`
|
|
28
42
|
|
|
29
43
|
# 0.28.1 2026-02-20
|
|
30
44
|
|
|
@@ -163,9 +177,9 @@
|
|
|
163
177
|
|
|
164
178
|
# 2025-06-03 Version 0.12
|
|
165
179
|
|
|
166
|
-
- Add buffer, maxlen params to `Stream#
|
|
167
|
-
- Add buffer param to `Stream#
|
|
168
|
-
- Remove `Stream#
|
|
180
|
+
- Add buffer, maxlen params to `Stream#read_line`
|
|
181
|
+
- Add buffer param to `Stream#read`
|
|
182
|
+
- Remove `Stream#resp_read_line`, `Stream#resp_read` methods
|
|
169
183
|
|
|
170
184
|
# 2025-06-02 Version 0.11.1
|
|
171
185
|
|
data/README.md
CHANGED
|
@@ -37,7 +37,7 @@ implementation that allows integration with the entire Ruby ecosystem.
|
|
|
37
37
|
- Excellent performance characteristics for concurrent I/O-bound applications.
|
|
38
38
|
- `Fiber::Scheduler` implementation to automatically integrate with the Ruby
|
|
39
39
|
ecosystem in a transparent fashion.
|
|
40
|
-
-
|
|
40
|
+
- [Connection](#connections) class with automatic buffer management for reading.
|
|
41
41
|
- Optimized I/O for encrypted SSL connections.
|
|
42
42
|
|
|
43
43
|
## Design
|
|
@@ -286,64 +286,70 @@ fiber = Fiber.schedule do
|
|
|
286
286
|
end
|
|
287
287
|
```
|
|
288
288
|
|
|
289
|
-
##
|
|
289
|
+
## Connections
|
|
290
290
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
provide an API that is useful for
|
|
294
|
-
(frame-based) protocols.
|
|
291
|
+
`UringMachine::Connection` is a class designed for efficiently read from and
|
|
292
|
+
write to a socket or other file descriptor. Connections are ideal for
|
|
293
|
+
implementing the read side of protocols, and provide an API that is useful for
|
|
294
|
+
both line-based protocols and binary (frame-based) protocols.
|
|
295
295
|
|
|
296
|
-
A
|
|
297
|
-
(see also [
|
|
298
|
-
advantage of io_uring's registered
|
|
299
|
-
introduction of [incremental buffer
|
|
296
|
+
A connection is associated with a UringMachine instance and a target file
|
|
297
|
+
descriptor (or SSL socket, see also [connection modes](#connection-modes)
|
|
298
|
+
below). Behind the scenes, connections take advantage of io_uring's registered
|
|
299
|
+
buffers feature, and more recently, the introduction of [incremental buffer
|
|
300
300
|
consumption](https://github.com/axboe/liburing/wiki/What's-new-with-io_uring-in-6.11-and-6.12#incremental-provided-buffer-consumption).
|
|
301
301
|
|
|
302
|
-
When
|
|
302
|
+
When connections are used, UringMachine automatically manages the buffers it
|
|
303
303
|
provides to the kernel, maximizing buffer reuse and minimizing allocations.
|
|
304
304
|
UringMachine also responds to stress conditions (increased incoming traffic) by
|
|
305
305
|
automatically provisioning additional buffers.
|
|
306
306
|
|
|
307
|
-
To create a
|
|
307
|
+
To create a connection for a given fd, use `UM#connection`:
|
|
308
308
|
|
|
309
309
|
```ruby
|
|
310
|
-
|
|
310
|
+
conn = machine.connection(fd)
|
|
311
311
|
|
|
312
|
-
# you can also provide a block that will be passed the
|
|
313
|
-
machine.
|
|
312
|
+
# you can also provide a block that will be passed the connection instance:
|
|
313
|
+
machine.connection(fd) { |c| do_something_with(c) }
|
|
314
314
|
|
|
315
|
-
# you can also instantiate a
|
|
316
|
-
|
|
315
|
+
# you can also instantiate a connection directly:
|
|
316
|
+
conn = UM::Connection.new(machine, fd)
|
|
317
317
|
```
|
|
318
318
|
|
|
319
|
-
The following API is used to interact with the
|
|
319
|
+
The following API is used to interact with the connection:
|
|
320
320
|
|
|
321
321
|
```ruby
|
|
322
322
|
# Read until a newline character is encountered:
|
|
323
|
-
line =
|
|
323
|
+
line = conn.read_line(0)
|
|
324
324
|
|
|
325
325
|
# Read line with a maximum length of 13 bytes:
|
|
326
|
-
line =
|
|
326
|
+
line = conn.read_line(13)
|
|
327
327
|
|
|
328
328
|
# Read all data:
|
|
329
|
-
buf =
|
|
329
|
+
buf = conn.read(0)
|
|
330
330
|
|
|
331
331
|
# Read exactly 13 bytes:
|
|
332
|
-
buf =
|
|
332
|
+
buf = conn.read(13)
|
|
333
333
|
|
|
334
334
|
# Read up to 13 bytes:
|
|
335
|
-
buf =
|
|
335
|
+
buf = conn.read(-13)
|
|
336
|
+
|
|
337
|
+
# Read continuously until EOF
|
|
338
|
+
conn.read_each { |data| ... }
|
|
336
339
|
|
|
337
340
|
# Skip 3 bytes:
|
|
338
|
-
|
|
341
|
+
conn.skip(3)
|
|
342
|
+
|
|
343
|
+
# Write
|
|
344
|
+
conn.write('foo', 'bar', 'baz')
|
|
339
345
|
```
|
|
340
346
|
|
|
341
347
|
Here's an example of a how a basic HTTP request parser might be implemented
|
|
342
|
-
using a
|
|
348
|
+
using a connection:
|
|
343
349
|
|
|
344
350
|
```ruby
|
|
345
|
-
def parse_http_request_headers(
|
|
346
|
-
request_line =
|
|
351
|
+
def parse_http_request_headers(conn)
|
|
352
|
+
request_line = conn.read_line(0)
|
|
347
353
|
m = request_line.match(REQUEST_LINE_RE)
|
|
348
354
|
return nil if !m
|
|
349
355
|
|
|
@@ -354,7 +360,7 @@ def parse_http_request_headers(stream)
|
|
|
354
360
|
}
|
|
355
361
|
|
|
356
362
|
while true
|
|
357
|
-
line =
|
|
363
|
+
line = conn.read_line(0)
|
|
358
364
|
break if !line || line.empty?
|
|
359
365
|
|
|
360
366
|
m = line.match(HEADER_RE)
|
|
@@ -364,24 +370,26 @@ def parse_http_request_headers(stream)
|
|
|
364
370
|
end
|
|
365
371
|
```
|
|
366
372
|
|
|
367
|
-
###
|
|
373
|
+
### Connection modes
|
|
368
374
|
|
|
369
|
-
|
|
370
|
-
three modes:
|
|
375
|
+
Connection modes allow connections to be transport agnostic. Currently
|
|
376
|
+
connections support three modes:
|
|
371
377
|
|
|
372
|
-
- `:
|
|
378
|
+
- `:fd` - use the buffer pool, read data using multishot read
|
|
373
379
|
(this is the default mode).
|
|
374
|
-
- `:
|
|
380
|
+
- `:socket` - use the buffer pool, read data using multishot recv.
|
|
375
381
|
- `:ssl` - read from an `SSLSocket` object.
|
|
376
382
|
|
|
377
|
-
The mode is specified as an additional argument to `
|
|
383
|
+
The mode is specified as an additional argument to `Connection.new`:
|
|
378
384
|
|
|
379
385
|
```ruby
|
|
380
|
-
#
|
|
381
|
-
|
|
386
|
+
# using recv/send:
|
|
387
|
+
conn = machine.connection(fd, :socket)
|
|
382
388
|
|
|
383
|
-
#
|
|
384
|
-
|
|
389
|
+
# SSL I/O:
|
|
390
|
+
conn = machine.connection(ssl, :ssl)
|
|
391
|
+
# or simply:
|
|
392
|
+
conn = machine.connection(ssl)
|
|
385
393
|
```
|
|
386
394
|
|
|
387
395
|
## Performance
|
data/TODO.md
CHANGED
|
@@ -1,67 +1,64 @@
|
|
|
1
1
|
## immediate
|
|
2
2
|
|
|
3
|
-
- Add support for returning a value on timeout
|
|
4
|
-
|
|
5
|
-
Since to do this safely we need to actually raise an exception that wraps the
|
|
6
|
-
value, rescue it and return the value, we might want a separate method that
|
|
7
|
-
wraps `#timeout`:
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
TimeoutValueError < StandardError
|
|
11
|
-
|
|
12
|
-
def timeout_with_value(interval, value, &block)
|
|
13
|
-
timeout_error = TimeoutValueError
|
|
14
|
-
timeout(interval, timeout_error, &block)
|
|
15
|
-
rescue TimeoutValueError => e
|
|
16
|
-
raise if e != timeout_error
|
|
17
|
-
|
|
18
|
-
value
|
|
19
|
-
end
|
|
20
|
-
```
|
|
21
|
-
|
|
22
3
|
- Add tests for support for Set in `machine#await`
|
|
23
4
|
- Add tests for support for Set, Array in `machine#join`
|
|
24
|
-
- Add
|
|
25
|
-
- Add
|
|
5
|
+
- Add `UM#read_file` for reading entire file
|
|
6
|
+
- Add `UM#write_file` for writing entire file
|
|
7
|
+
- Rename stream methods: `:fd`, `:socket`, `:ssl`
|
|
26
8
|
|
|
27
|
-
|
|
9
|
+
## Improving streams
|
|
28
10
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
11
|
+
One wart of the stream API is that it's only for reading, so if we want to
|
|
12
|
+
implement a protocol where we read and write to a target fd, we also need to
|
|
13
|
+
keep the fd around or call `stream.target` every time we want to write to it,
|
|
14
|
+
*and* we don't have a transport-agnostic write op.
|
|
32
15
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- :buffer_pool - uses buffer rings
|
|
36
|
-
- :ssl - read from an SSL connection (`SSLSocket`)
|
|
37
|
-
- :io - read from an `IO`
|
|
38
|
-
|
|
39
|
-
The API will look something like:
|
|
16
|
+
What if instead of `Stream` we had something called `Link`, which serves for
|
|
17
|
+
both reading and writing:
|
|
40
18
|
|
|
41
19
|
```ruby
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
stream = UM::Stream.new(machine, str) # string mode
|
|
49
|
-
stream = UM::Stream.new(machine, io_buf) # IO:Buffer mode
|
|
20
|
+
conn = machine.connection(fd)
|
|
21
|
+
while l = conn.read_line
|
|
22
|
+
conn.write(l, '\n')
|
|
23
|
+
end
|
|
24
|
+
# or:
|
|
25
|
+
buf = conn.read(42)
|
|
50
26
|
```
|
|
51
27
|
|
|
52
|
-
|
|
28
|
+
RESP:
|
|
53
29
|
|
|
54
30
|
```ruby
|
|
55
|
-
|
|
31
|
+
conn.resp_write(['foo', 'bar'])
|
|
32
|
+
reply = conn.resp_read
|
|
56
33
|
```
|
|
57
34
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
35
|
+
HTTP:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
r = conn.http_read_request
|
|
39
|
+
conn.http_write_response({ ':status' => 200 }, 'foo')
|
|
61
40
|
|
|
62
|
-
|
|
41
|
+
# or:
|
|
42
|
+
conn.http_write_request({ ':method' => 'GET', ':path' => '/foo' }, nil)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Plan of action:
|
|
46
|
+
|
|
47
|
+
- Rename methods:
|
|
48
|
+
- [v] rename `#read_line` to `#read_line`
|
|
49
|
+
- [v] rename `#read` to `#read`
|
|
50
|
+
- [v] rename `#read_to_delim` to `#read_to_delim`
|
|
51
|
+
- [v] rename `#each` to `#read_each`
|
|
52
|
+
- [v] rename `#resp_decode` to `#resp_read`
|
|
53
|
+
- Rename modes:
|
|
54
|
+
- [v] :fd to :fd
|
|
55
|
+
- [v] :socket to :socket
|
|
56
|
+
- [v] auto detect SSL
|
|
57
|
+
- Rename `Stream` to `Connection`
|
|
58
|
+
- Add methods:
|
|
59
|
+
- `#write(*bufs)`
|
|
60
|
+
- `#resp_write(obj)`
|
|
63
61
|
|
|
64
|
-
>>>>>>> 04d9eb7 (Docs)
|
|
65
62
|
## Balancing I/O with the runqueue
|
|
66
63
|
|
|
67
64
|
- in some cases where there are many entries in the runqueue, this can
|
|
@@ -89,6 +86,8 @@ Continued discussion in docs/design/buffer_pool.md
|
|
|
89
86
|
debouncer = machine.debounce { }
|
|
90
87
|
```
|
|
91
88
|
|
|
89
|
+
- happy eyeballs algo for TCP connect
|
|
90
|
+
|
|
92
91
|
- read multiple files
|
|
93
92
|
|
|
94
93
|
```ruby
|
|
@@ -99,12 +98,31 @@ Continued discussion in docs/design/buffer_pool.md
|
|
|
99
98
|
machine.read_files(*fns) #=> { fn1:, fn2:, fn3:, ...}
|
|
100
99
|
```
|
|
101
100
|
|
|
101
|
+
- more generally, a DSL for expressing batch operations:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
result = machine.batch do |b|
|
|
105
|
+
fns.each { b[it] = read_file(b, it) }
|
|
106
|
+
end
|
|
107
|
+
#=> { fn1 => data1, fn2 => data2, ... }
|
|
108
|
+
|
|
109
|
+
# we can also imagine performing operations in sequence using linking:
|
|
110
|
+
result = machine.batch {
|
|
111
|
+
m.
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
102
117
|
## polyvalent select
|
|
103
118
|
|
|
104
119
|
- select on multiple queues (ala Go)
|
|
105
120
|
- select on mixture of queues and fds
|
|
121
|
+
- select on fibers:
|
|
122
|
+
- select fibers that are done
|
|
123
|
+
- select first done fiber
|
|
106
124
|
|
|
107
|
-
## ops
|
|
125
|
+
## ops still not implemented
|
|
108
126
|
|
|
109
127
|
- splice / - tee
|
|
110
128
|
- sendto
|
|
@@ -123,31 +141,7 @@ Continued discussion in docs/design/buffer_pool.md
|
|
|
123
141
|
When doing a `call`, we need to provide a mailbox for the response. can this be
|
|
124
142
|
automatic?
|
|
125
143
|
|
|
126
|
-
##
|
|
127
|
-
|
|
128
|
-
We're still missing:
|
|
129
|
-
|
|
130
|
-
- limit on line length in `get_line`
|
|
131
|
-
- ability to supply buffer to `get_line` and `get_string`
|
|
132
|
-
- allow read to eof, maybe with `read_to_eof`
|
|
133
|
-
|
|
134
|
-
For the sake of performance, simplicity and explicitness, we change the API as
|
|
135
|
-
follows:
|
|
136
|
-
|
|
137
|
-
```ruby
|
|
138
|
-
stream.get_line(buf, limit)
|
|
139
|
-
# the defaults:
|
|
140
|
-
stream.get_line(nil, -1)
|
|
141
|
-
|
|
142
|
-
stream.get_string(len, buf)
|
|
143
|
-
# defaults:
|
|
144
|
-
stream.get_string(len, nil)
|
|
145
|
-
|
|
146
|
-
# and
|
|
147
|
-
stream.read_to_eof(buf)
|
|
148
|
-
# defaults:
|
|
149
|
-
stream.read_to_eof(nil)
|
|
150
|
-
```
|
|
144
|
+
##
|
|
151
145
|
|
|
152
146
|
## Syntax / pattern for launching/supervising multiple operations
|
|
153
147
|
|
|
@@ -166,6 +160,5 @@ machine.shift_select(*queues) #=> [result, queue]
|
|
|
166
160
|
```ruby
|
|
167
161
|
# addrs: [['1.1.1.1', 80], ['2.2.2.2', 80]]
|
|
168
162
|
# ['1.1.1.1:80', '2.2.2.2:80']
|
|
169
|
-
|
|
163
|
+
tcp_connect_he(*addrs)
|
|
170
164
|
```
|
|
171
|
-
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative './common'
|
|
4
|
+
require 'socket'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
require 'localhost/authority'
|
|
7
|
+
|
|
8
|
+
GROUPS = 48
|
|
9
|
+
ITERATIONS = 5000
|
|
10
|
+
|
|
11
|
+
SIZE = 1 << 14
|
|
12
|
+
DATA = '*' * SIZE
|
|
13
|
+
|
|
14
|
+
class UMBenchmark
|
|
15
|
+
def server_ctx
|
|
16
|
+
@server_ctx ||= Localhost::Authority.fetch.server_context
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ssl_wrap(sock, ctx)
|
|
20
|
+
OpenSSL::SSL::SSLSocket.new(sock, ctx).tap { it.sync_close = true }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ssl_socketpair(machine)
|
|
24
|
+
sock1, sock2 = Socket.socketpair(:AF_UNIX, :SOCK_STREAM, 0)
|
|
25
|
+
ssl1 = ssl_wrap(sock1, server_ctx)
|
|
26
|
+
ssl2 = ssl_wrap(sock2, OpenSSL::SSL::SSLContext.new)
|
|
27
|
+
|
|
28
|
+
if !machine
|
|
29
|
+
t = Thread.new { ssl1.accept rescue nil }
|
|
30
|
+
ssl2.connect
|
|
31
|
+
t.join
|
|
32
|
+
else
|
|
33
|
+
machine.ssl_set_bio(ssl1)
|
|
34
|
+
machine.ssl_set_bio(ssl2)
|
|
35
|
+
f = machine.spin { ssl1.accept rescue nil }
|
|
36
|
+
ssl2.connect
|
|
37
|
+
machine.join(f)
|
|
38
|
+
end
|
|
39
|
+
[ssl1, ssl2]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def do_threads(threads, ios)
|
|
43
|
+
GROUPS.times do
|
|
44
|
+
r, w = ssl_socketpair(nil)
|
|
45
|
+
threads << Thread.new do
|
|
46
|
+
ITERATIONS.times { w.write(DATA) }
|
|
47
|
+
w.close
|
|
48
|
+
end
|
|
49
|
+
threads << Thread.new do
|
|
50
|
+
ITERATIONS.times { r.readpartial(SIZE) }
|
|
51
|
+
r.close
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def do_thread_pool(thread_pool, ios)
|
|
57
|
+
GROUPS.times do
|
|
58
|
+
r, w = ssl_socketpair(nil)
|
|
59
|
+
r.sync = true
|
|
60
|
+
w.sync = true
|
|
61
|
+
ios << r << w
|
|
62
|
+
ITERATIONS.times {
|
|
63
|
+
thread_pool.queue { w.write(DATA) }
|
|
64
|
+
thread_pool.queue { r.readpartial(SIZE) }
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def do_scheduler(scheduler, ios)
|
|
70
|
+
GROUPS.times do
|
|
71
|
+
r, w = ssl_socketpair(nil)
|
|
72
|
+
r.sync = true
|
|
73
|
+
w.sync = true
|
|
74
|
+
Fiber.schedule do
|
|
75
|
+
ITERATIONS.times { w.write(DATA) }
|
|
76
|
+
w.close
|
|
77
|
+
end
|
|
78
|
+
Fiber.schedule do
|
|
79
|
+
ITERATIONS.times { r.readpartial(SIZE) }
|
|
80
|
+
r.close
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def do_scheduler_x(div, scheduler, ios)
|
|
86
|
+
(GROUPS/div).times do
|
|
87
|
+
r, w = ssl_socketpair(nil)
|
|
88
|
+
r.sync = true
|
|
89
|
+
w.sync = true
|
|
90
|
+
Fiber.schedule do
|
|
91
|
+
ITERATIONS.times { w.write(DATA) }
|
|
92
|
+
w.close
|
|
93
|
+
end
|
|
94
|
+
Fiber.schedule do
|
|
95
|
+
ITERATIONS.times { r.readpartial(SIZE) }
|
|
96
|
+
r.close
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def do_um(machine, fibers, fds)
|
|
102
|
+
GROUPS.times do
|
|
103
|
+
r, w = ssl_socketpair(machine)
|
|
104
|
+
fibers << machine.spin do
|
|
105
|
+
ITERATIONS.times { machine.ssl_write(w, DATA, SIZE) }
|
|
106
|
+
machine.close_async(w)
|
|
107
|
+
end
|
|
108
|
+
fibers << machine.spin do
|
|
109
|
+
ITERATIONS.times { machine.ssl_read(r, +'', SIZE) }
|
|
110
|
+
machine.close_async(r)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def do_um_x(div, machine, fibers, fds)
|
|
116
|
+
(GROUPS/div).times do
|
|
117
|
+
r, w = ssl_socketpair(machine)
|
|
118
|
+
fibers << machine.spin do
|
|
119
|
+
ITERATIONS.times { machine.ssl_write(w, DATA, SIZE) }
|
|
120
|
+
machine.close_async(w)
|
|
121
|
+
end
|
|
122
|
+
fibers << machine.spin do
|
|
123
|
+
ITERATIONS.times { machine.ssl_read(r, +'', SIZE) }
|
|
124
|
+
machine.close_async(r)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative './common'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
|
|
6
|
+
C = ENV['C']&.to_i || 50
|
|
7
|
+
I = 10
|
|
8
|
+
puts "C=#{C}"
|
|
9
|
+
|
|
10
|
+
class UMBenchmark
|
|
11
|
+
CONTAINER_NAME = "redis-#{SecureRandom.hex}"
|
|
12
|
+
|
|
13
|
+
def start_redis_server
|
|
14
|
+
`docker run --name #{CONTAINER_NAME} -d -p 6379:6379 redis:latest`
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def stop_redis_server
|
|
18
|
+
`docker stop #{CONTAINER_NAME}`
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_redis_conn(retries = 0)
|
|
22
|
+
Redis.new
|
|
23
|
+
rescue
|
|
24
|
+
if retries < 3
|
|
25
|
+
sleep 0.5
|
|
26
|
+
create_redis_conn(retries + 1)
|
|
27
|
+
else
|
|
28
|
+
raise
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def query_redis(conn)
|
|
33
|
+
conn.set('abc', 'def')
|
|
34
|
+
p conn.get('abc')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def with_container
|
|
38
|
+
start_redis_server
|
|
39
|
+
sleep 0.5
|
|
40
|
+
yield
|
|
41
|
+
rescue Exception => e
|
|
42
|
+
p e
|
|
43
|
+
p e.backtrace
|
|
44
|
+
ensure
|
|
45
|
+
stop_redis_server
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def benchmark
|
|
49
|
+
with_container {
|
|
50
|
+
Benchmark.bm { run_benchmarks(it) }
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# def do_threads(threads, ios)
|
|
55
|
+
# C.times.map do
|
|
56
|
+
# threads << Thread.new do
|
|
57
|
+
# conn = create_redis_conn
|
|
58
|
+
# I.times { query_redis(conn) }
|
|
59
|
+
# ensure
|
|
60
|
+
# conn.close
|
|
61
|
+
# end
|
|
62
|
+
# end
|
|
63
|
+
# end
|
|
64
|
+
|
|
65
|
+
def do_scheduler(scheduler, ios)
|
|
66
|
+
return if !scheduler.is_a?(UM::FiberScheduler)
|
|
67
|
+
C.times do
|
|
68
|
+
Fiber.schedule do
|
|
69
|
+
conn = create_redis_conn
|
|
70
|
+
I.times { query_redis(conn) }
|
|
71
|
+
ensure
|
|
72
|
+
conn.close
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/benchmark/common.rb
CHANGED
data/benchmark/gets.rb
CHANGED
|
@@ -34,16 +34,16 @@ def um_read
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
@
|
|
38
|
-
@
|
|
39
|
-
def
|
|
40
|
-
@
|
|
37
|
+
@fd_connection = @machine.open('/dev/random', UM::O_RDONLY)
|
|
38
|
+
@conn = UM::Connection.new(@machine, @fd_connection)
|
|
39
|
+
def um_connection_read_line
|
|
40
|
+
@conn.read_line(0)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
Benchmark.ips do |x|
|
|
44
|
-
x.report('IO#gets')
|
|
45
|
-
x.report('UM#read+buf')
|
|
46
|
-
x.report('UM::
|
|
44
|
+
x.report('IO#gets') { io_gets }
|
|
45
|
+
x.report('UM#read+buf') { um_read }
|
|
46
|
+
x.report('UM::Connection') { um_connection_read_line }
|
|
47
47
|
|
|
48
48
|
x.compare!(order: :baseline)
|
|
49
49
|
end
|
|
@@ -83,40 +83,40 @@ ensure
|
|
|
83
83
|
stop_server
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
@
|
|
87
|
-
def
|
|
86
|
+
@total_connection = 0
|
|
87
|
+
def um_connection_do
|
|
88
88
|
# fd = @machine.open('/dev/random', UM::O_RDONLY)
|
|
89
89
|
fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
|
90
90
|
@machine.connect(fd, '127.0.0.1', 1234)
|
|
91
|
-
|
|
92
|
-
N.times { @
|
|
91
|
+
conn = UM::Connection.new(@machine, fd)
|
|
92
|
+
N.times { @total_connection += conn.read_line(0)&.bytesize || 0 }
|
|
93
93
|
rescue => e
|
|
94
94
|
p e
|
|
95
95
|
p e.backtrace
|
|
96
96
|
ensure
|
|
97
|
-
|
|
97
|
+
conn.clear
|
|
98
98
|
@machine.close(fd)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
def
|
|
101
|
+
def um_connection
|
|
102
102
|
start_server
|
|
103
103
|
ff = C.times.map {
|
|
104
104
|
@machine.snooze
|
|
105
|
-
@machine.spin {
|
|
105
|
+
@machine.spin { um_connection_do }
|
|
106
106
|
}
|
|
107
107
|
@machine.await(ff)
|
|
108
|
-
pp total: @
|
|
108
|
+
pp total: @total_connection
|
|
109
109
|
ensure
|
|
110
110
|
stop_server
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
p(C:, N:)
|
|
114
|
-
|
|
114
|
+
um_connection
|
|
115
115
|
pp @machine.metrics
|
|
116
116
|
exit
|
|
117
117
|
|
|
118
118
|
Benchmark.bm do
|
|
119
119
|
it.report('Thread/IO#gets') { io_gets }
|
|
120
120
|
it.report('Fiber/UM#read+buf') { um_read }
|
|
121
|
-
it.report('Fiber/UM::Stream') {
|
|
121
|
+
it.report('Fiber/UM::Stream') { um_connection }
|
|
122
122
|
end
|