source: benchmark/io/batch-readv.c @ c33ed65

ADTarm-ehast-experimentalenumforall-pointer-decayjacob/cs343-translationnew-astnew-ast-unique-exprpthread-emulationqualifiedEnum
Last change on this file since c33ed65 was c33ed65, checked in by Thierry Delisle <tdelisle@…>, 4 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.