source: benchmark/io/batch-readv.c@ 31bb2e1

ADT arm-eh ast-experimental enum forall-pointer-decay jacob/cs343-translation new-ast new-ast-unique-expr pthread-emulation qualifiedEnum
Last change on this file since 31bb2e1 was c33ed65, checked in by Thierry Delisle <tdelisle@…>, 5 years ago

Added benchmark to check io_uring batchsize

  • Property mode set to 100644
File size: 9.8 KB
Line 
1// Program to test the optimial batchsize in a single threaded process
2extern "C" {
3 #ifndef _GNU_SOURCE /* See feature_test_macros(7) */
4 #define _GNU_SOURCE /* See feature_test_macros(7) */
5 #endif
6 #include <errno.h>
7 #include <stdio.h>
8 #include <stdint.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <locale.h>
12 #include <getopt.h>
13 #include <unistd.h>
14 #include <sys/mman.h>
15 #include <sys/syscall.h>
16 #include <sys/uio.h>
17 #include <fcntl.h>
18 #include <time.h> // timespec
19 #include <sys/time.h> // timeval
20
21 #include <linux/io_uring.h>
22}
23
24
25enum { TIMEGRAN = 1000000000LL }; // nanosecond granularity, except for timeval
26
27#include <omp.h>
28
29# ifndef __NR_io_uring_setup
30# define __NR_io_uring_setup 425
31# endif
32# ifndef __NR_io_uring_enter
33# define __NR_io_uring_enter 426
34# endif
35# ifndef __NR_io_uring_register
36# define __NR_io_uring_register 427
37# endif
38
39struct io_uring_sq {
40 // Head and tail of the ring (associated with array)
41 volatile uint32_t * head;
42 volatile uint32_t * tail;
43
44 // The actual kernel ring which uses head/tail
45 // indexes into the sqes arrays
46 uint32_t * array;
47
48 // number of entries and mask to go with it
49 const uint32_t * num;
50 const uint32_t * mask;
51
52 // Submission flags (Not sure what for)
53 uint32_t * flags;
54
55 // number of sqes not submitted (whatever that means)
56 uint32_t * dropped;
57
58 // Like head/tail but not seen by the kernel
59 volatile uint32_t alloc;
60
61 // A buffer of sqes (not the actual ring)
62 struct io_uring_sqe * sqes;
63
64 // The location and size of the mmaped area
65 void * ring_ptr;
66 size_t ring_sz;
67};
68
69struct io_uring_cq {
70 // Head and tail of the ring
71 volatile uint32_t * head;
72 volatile uint32_t * tail;
73
74 // number of entries and mask to go with it
75 const uint32_t * mask;
76 const uint32_t * num;
77
78 // number of cqes not submitted (whatever that means)
79 uint32_t * overflow;
80
81 // the kernel ring
82 struct io_uring_cqe * cqes;
83
84 // The location and size of the mmaped area
85 void * ring_ptr;
86 size_t ring_sz;
87};
88
89struct io_ring {
90 struct io_uring_sq submit_q;
91 struct io_uring_cq completion_q;
92 uint32_t flags;
93 int fd;
94};
95
96struct fred {
97 io_ring io;
98};
99
100fred self;
101int myfd;
102
103long long unsigned submits = 0;
104long long unsigned completes = 0;
105
106void submit_and_drain(struct iovec * iov, int n) {
107 for(int i = 0; i < n; i++) {
108 struct io_uring_sqe * sqe = &self.io.submit_q.sqes[ 0 ];
109
110 sqe->opcode = IORING_OP_READV;
111 #if !defined(IOSQE_ASYNC)
112 sqe->flags = 0;
113 #else
114 sqe->flags = IOSQE_ASYNC;
115 #endif
116 sqe->ioprio = 0;
117 sqe->fd = myfd;
118 sqe->off = 0;
119 sqe->addr = (__u64)iov;
120 sqe->len = 1;
121 sqe->rw_flags = 0;
122 sqe->__pad2[0] = sqe->__pad2[1] = sqe->__pad2[2] = 0;
123 }
124
125 volatile uint32_t * tail = self.io.submit_q.tail;
126 __atomic_fetch_add(tail, n, __ATOMIC_SEQ_CST);
127
128 int ret = syscall( __NR_io_uring_enter, self.io.fd, n, n, IORING_ENTER_GETEVENTS, nullptr, 0);
129 if( ret < 0 ) {
130 switch((int)errno) {
131 case EAGAIN:
132 case EINTR:
133 default:
134 fprintf(stderr, "KERNEL ERROR: IO_URING WAIT - %s\n", strerror(errno) );
135 abort();
136 }
137 }
138
139 submits += ret;
140
141 uint32_t chead = *self.io.completion_q.head;
142 uint32_t ctail = *self.io.completion_q.tail;
143 const uint32_t mask = *self.io.completion_q.mask;
144
145 // Memory barrier
146 __atomic_thread_fence( __ATOMIC_SEQ_CST );
147
148 uint32_t count = ctail - chead;
149 __atomic_fetch_add( self.io.completion_q.head, count, __ATOMIC_RELAXED );
150 completes += count;
151}
152
153uint64_t getTimeNsec() {
154 timespec curr;
155 clock_gettime( CLOCK_REALTIME, &curr );
156 return (int64_t)curr.tv_sec * TIMEGRAN + curr.tv_nsec;
157}
158
159uint64_t to_miliseconds( uint64_t durtn ) { return durtn / (TIMEGRAN / 1000LL); }
160double to_fseconds(uint64_t durtn ) { return durtn / (double)TIMEGRAN; }
161uint64_t from_fseconds(double sec) { return sec * TIMEGRAN; }
162
163int main(int argc, char * argv[]) {
164 int buflen = 50;
165 int batch = 1;
166 double duration = 5;
167
168 setlocale(LC_ALL, "");
169
170 for(;;) {
171 static struct option options[] = {
172 {"duration", required_argument, 0, 'd'},
173 {"batchsize", required_argument, 0, 'b'},
174 {"buflen", required_argument, 0, 'l'},
175 {0, 0, 0, 0}
176 };
177
178 int idx = 0;
179 int opt = getopt_long(argc, argv, "d:l:b:", options, &idx);
180
181 const char * arg = optarg ? optarg : "";
182 char * end;
183 switch(opt) {
184 // Exit Case
185 case -1:
186 goto arg_loop;
187 case 'd': \
188 duration = strtod(arg, &end); \
189 if(*end != '\0') { \
190 fprintf(stderr, "Duration must be a valid double, was %s\n", arg); \
191 goto usage; \
192 } \
193 break;
194 case 'l':
195 buflen = strtoul(arg, &end, 10);
196 if(*end != '\0' && buflen < 10) {
197 fprintf(stderr, "Buffer size must be at least 10, was %s\n", arg);
198 goto usage;
199 }
200 case 'b':
201 batch = strtoul(arg, &end, 10);
202 if(*end != '\0' && batch < 0) {
203 fprintf(stderr, "Batch size must be at least 1, was %s\n", arg);
204 goto usage;
205 }
206 break;
207 default: /* ? */
208 fprintf(stderr, "%d\n", opt);
209 usage:
210 fprintf( stderr, " -l, --buflen=SIZE Number of bytes to read per request\n" );
211 fprintf( stderr, " -b, --batchsize=COUNT Number of request to batch together\n" );
212 exit(EXIT_FAILURE);
213 }
214 }
215 arg_loop:
216
217 myfd = open(__FILE__, 0);
218
219 // Step 1 : call to setup
220 struct io_uring_params params;
221 memset(&params, 0, sizeof(params));
222
223 uint32_t nentries = 2048;
224
225 int fd = syscall(__NR_io_uring_setup, nentries, &params );
226 if(fd < 0) {
227 fprintf(stderr, "KERNEL ERROR: IO_URING SETUP - %s\n", strerror(errno));
228 abort();
229 }
230
231 // Step 2 : mmap result
232 memset(&self.io, 0, sizeof(struct io_ring));
233 struct io_uring_sq & sq = self.io.submit_q;
234 struct io_uring_cq & cq = self.io.completion_q;
235
236 // calculate the right ring size
237 sq.ring_sz = params.sq_off.array + (params.sq_entries * sizeof(unsigned) );
238 cq.ring_sz = params.cq_off.cqes + (params.cq_entries * sizeof(struct io_uring_cqe));
239
240 // Requires features
241 // // adjust the size according to the parameters
242 // if ((params.features & IORING_FEAT_SINGLE_MMAP) != 0) {
243 // cq->ring_sz = sq->ring_sz = max(cq->ring_sz, sq->ring_sz);
244 // }
245
246 // mmap the Submit Queue into existence
247 sq.ring_ptr = mmap(0, sq.ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_SQ_RING);
248 if (sq.ring_ptr == (void*)MAP_FAILED) {
249 fprintf(stderr, "KERNEL ERROR: IO_URING MMAP1 - %s\n", strerror(errno));
250 abort();
251 }
252
253 // mmap the Completion Queue into existence (may or may not be needed)
254 // Requires features
255 // if ((params.features & IORING_FEAT_SINGLE_MMAP) != 0) {
256 // cq->ring_ptr = sq->ring_ptr;
257 // }
258 // else {
259 // We need multiple call to MMAP
260 cq.ring_ptr = mmap(0, cq.ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_CQ_RING);
261 if (cq.ring_ptr == (void*)MAP_FAILED) {
262 munmap(sq.ring_ptr, sq.ring_sz);
263 fprintf(stderr, "KERNEL ERROR: IO_URING MMAP2 - %s\n", strerror(errno));
264 abort();
265 }
266 // }
267
268 // mmap the submit queue entries
269 size_t size = params.sq_entries * sizeof(struct io_uring_sqe);
270 sq.sqes = (struct io_uring_sqe *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_SQES);
271 if (sq.sqes == (struct io_uring_sqe *)MAP_FAILED) {
272 munmap(sq.ring_ptr, sq.ring_sz);
273 if (cq.ring_ptr != sq.ring_ptr) munmap(cq.ring_ptr, cq.ring_sz);
274 fprintf(stderr, "KERNEL ERROR: IO_URING MMAP3 - %s\n", strerror(errno));
275 abort();
276 }
277
278 // Get the pointers from the kernel to fill the structure
279 // submit queue
280 sq.head = (volatile uint32_t *)(((intptr_t)sq.ring_ptr) + params.sq_off.head);
281 sq.tail = (volatile uint32_t *)(((intptr_t)sq.ring_ptr) + params.sq_off.tail);
282 sq.mask = ( const uint32_t *)(((intptr_t)sq.ring_ptr) + params.sq_off.ring_mask);
283 sq.num = ( const uint32_t *)(((intptr_t)sq.ring_ptr) + params.sq_off.ring_entries);
284 sq.flags = ( uint32_t *)(((intptr_t)sq.ring_ptr) + params.sq_off.flags);
285 sq.dropped = ( uint32_t *)(((intptr_t)sq.ring_ptr) + params.sq_off.dropped);
286 sq.array = ( uint32_t *)(((intptr_t)sq.ring_ptr) + params.sq_off.array);
287 sq.alloc = *sq.tail;
288
289 // completion queue
290 cq.head = (volatile uint32_t *)(((intptr_t)cq.ring_ptr) + params.cq_off.head);
291 cq.tail = (volatile uint32_t *)(((intptr_t)cq.ring_ptr) + params.cq_off.tail);
292 cq.mask = ( const uint32_t *)(((intptr_t)cq.ring_ptr) + params.cq_off.ring_mask);
293 cq.num = ( const uint32_t *)(((intptr_t)cq.ring_ptr) + params.cq_off.ring_entries);
294 cq.overflow = ( uint32_t *)(((intptr_t)cq.ring_ptr) + params.cq_off.overflow);
295 cq.cqes = (struct io_uring_cqe *)(((intptr_t)cq.ring_ptr) + params.cq_off.cqes);
296
297 self.io.fd = fd;
298
299 // Allocate the sqe
300 uint32_t idx = 0;
301
302 // Return the sqe
303 struct io_uring_sqe * sqe = &self.io.submit_q.sqes[ idx & (*self.io.submit_q.mask)];
304
305 char data[buflen];
306 struct iovec iov = { data, (size_t)buflen };
307
308 sqe->opcode = IORING_OP_READV;
309 #if !defined(IOSQE_ASYNC)
310 sqe->flags = 0;
311 #else
312 sqe->flags = IOSQE_ASYNC;
313 #endif
314 sqe->ioprio = 0;
315 sqe->fd = myfd;
316 sqe->off = 0;
317 sqe->addr = (__u64)&iov;
318 sqe->len = 1;
319 sqe->rw_flags = 0;
320 sqe->__pad2[0] = sqe->__pad2[1] = sqe->__pad2[2] = 0;
321
322 // Append to the list of ready entries
323 for(unsigned i = 0; i < *self.io.submit_q.num; i++) {
324 self.io.submit_q.array[ i ] = 0;
325 }
326
327 printf("Running for %f second, reading %d bytes in batches of %d\n", duration, buflen, batch);
328 uint64_t start = getTimeNsec();
329 uint64_t end = getTimeNsec();
330 uint64_t prev = getTimeNsec();
331 for(;;) {
332 submit_and_drain(&iov, batch);
333 end = getTimeNsec();
334 uint64_t delta = end - start;
335 if( to_fseconds(end - prev) > 0.1 ) {
336 printf(" %.1f\r", to_fseconds(delta));
337 fflush(stdout);
338 prev = end;
339 }
340 if( delta >= from_fseconds(duration) ) {
341 break;
342 }
343 }
344
345 printf("Took %'ld ms\n", to_miliseconds(end - start));
346 printf("Submitted %'llu\n", submits);
347 printf("Completed %'llu\n", completes);
348 printf("Submitted / sec %'.f\n", submits / to_fseconds(end - start));
349 printf("Completed / sec %'.f\n", completes / to_fseconds(end - start));
350}
Note: See TracBrowser for help on using the repository browser.