1 1.1 christos /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 1.1 christos * 3 1.1 christos * Permission is hereby granted, free of charge, to any person obtaining a copy 4 1.1 christos * of this software and associated documentation files (the "Software"), to 5 1.1 christos * deal in the Software without restriction, including without limitation the 6 1.1 christos * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 1.1 christos * sell copies of the Software, and to permit persons to whom the Software is 8 1.1 christos * furnished to do so, subject to the following conditions: 9 1.1 christos * 10 1.1 christos * The above copyright notice and this permission notice shall be included in 11 1.1 christos * all copies or substantial portions of the Software. 12 1.1 christos * 13 1.1 christos * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 1.1 christos * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 1.1 christos * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 1.1 christos * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 1.1 christos * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 1.1 christos * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 1.1 christos * IN THE SOFTWARE. 20 1.1 christos */ 21 1.1 christos 22 1.1 christos #include "task.h" 23 1.1 christos #include "uv.h" 24 1.1 christos 25 1.1 christos #define IPC_PIPE_NAME TEST_PIPENAME 26 1.1 christos #define NUM_CONNECTS (250 * 1000) 27 1.1 christos 28 1.1 christos union stream_handle { 29 1.1 christos uv_pipe_t pipe; 30 1.1 christos uv_tcp_t tcp; 31 1.1 christos }; 32 1.1 christos 33 1.1 christos /* Use as (uv_stream_t *) &handle_storage -- it's kind of clunky but it 34 1.1 christos * avoids aliasing warnings. 35 1.1 christos */ 36 1.1 christos typedef unsigned char handle_storage_t[sizeof(union stream_handle)]; 37 1.1 christos 38 1.1 christos /* Used for passing around the listen handle, not part of the benchmark proper. 39 1.1 christos * We have an overabundance of server types here. It works like this: 40 1.1 christos * 41 1.1 christos * 1. The main thread starts an IPC pipe server. 42 1.1 christos * 2. The worker threads connect to the IPC server and obtain a listen handle. 43 1.1 christos * 3. The worker threads start accepting requests on the listen handle. 44 1.1 christos * 4. The main thread starts connecting repeatedly. 45 1.1 christos * 46 1.1 christos * Step #4 should perhaps be farmed out over several threads. 47 1.1 christos */ 48 1.1 christos struct ipc_server_ctx { 49 1.1 christos handle_storage_t server_handle; 50 1.1 christos unsigned int num_connects; 51 1.1 christos uv_pipe_t ipc_pipe; 52 1.1 christos }; 53 1.1 christos 54 1.1 christos struct ipc_peer_ctx { 55 1.1 christos handle_storage_t peer_handle; 56 1.1 christos uv_write_t write_req; 57 1.1 christos }; 58 1.1 christos 59 1.1 christos struct ipc_client_ctx { 60 1.1 christos uv_connect_t connect_req; 61 1.1 christos uv_stream_t* server_handle; 62 1.1 christos uv_pipe_t ipc_pipe; 63 1.1 christos char scratch[16]; 64 1.1 christos }; 65 1.1 christos 66 1.1 christos /* Used in the actual benchmark. */ 67 1.1 christos struct server_ctx { 68 1.1 christos handle_storage_t server_handle; 69 1.1 christos unsigned int num_connects; 70 1.1 christos uv_async_t async_handle; 71 1.1 christos uv_thread_t thread_id; 72 1.1 christos uv_sem_t semaphore; 73 1.1 christos }; 74 1.1 christos 75 1.1 christos struct client_ctx { 76 1.1 christos handle_storage_t client_handle; 77 1.1 christos unsigned int num_connects; 78 1.1 christos uv_connect_t connect_req; 79 1.1 christos uv_idle_t idle_handle; 80 1.1 christos }; 81 1.1 christos 82 1.1 christos static void ipc_connection_cb(uv_stream_t* ipc_pipe, int status); 83 1.1 christos static void ipc_write_cb(uv_write_t* req, int status); 84 1.1 christos static void ipc_close_cb(uv_handle_t* handle); 85 1.1 christos static void ipc_connect_cb(uv_connect_t* req, int status); 86 1.1 christos static void ipc_read_cb(uv_stream_t* handle, 87 1.1 christos ssize_t nread, 88 1.1 christos const uv_buf_t* buf); 89 1.1 christos static void ipc_alloc_cb(uv_handle_t* handle, 90 1.1 christos size_t suggested_size, 91 1.1 christos uv_buf_t* buf); 92 1.1 christos 93 1.1 christos static void sv_async_cb(uv_async_t* handle); 94 1.1 christos static void sv_connection_cb(uv_stream_t* server_handle, int status); 95 1.1 christos static void sv_read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf); 96 1.1 christos static void sv_alloc_cb(uv_handle_t* handle, 97 1.1 christos size_t suggested_size, 98 1.1 christos uv_buf_t* buf); 99 1.1 christos 100 1.1 christos static void cl_connect_cb(uv_connect_t* req, int status); 101 1.1 christos static void cl_idle_cb(uv_idle_t* handle); 102 1.1 christos static void cl_close_cb(uv_handle_t* handle); 103 1.1 christos 104 1.1 christos static struct sockaddr_in listen_addr; 105 1.1 christos 106 1.1 christos 107 1.1 christos static void ipc_connection_cb(uv_stream_t* ipc_pipe, int status) { 108 1.1 christos struct ipc_server_ctx* sc; 109 1.1 christos struct ipc_peer_ctx* pc; 110 1.1 christos uv_loop_t* loop; 111 1.1 christos uv_buf_t buf; 112 1.1 christos 113 1.1 christos loop = ipc_pipe->loop; 114 1.1 christos buf = uv_buf_init("PING", 4); 115 1.1 christos sc = container_of(ipc_pipe, struct ipc_server_ctx, ipc_pipe); 116 1.1 christos pc = calloc(1, sizeof(*pc)); 117 1.1.1.2 christos ASSERT_NOT_NULL(pc); 118 1.1 christos 119 1.1 christos if (ipc_pipe->type == UV_TCP) 120 1.1 christos ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) &pc->peer_handle)); 121 1.1 christos else if (ipc_pipe->type == UV_NAMED_PIPE) 122 1.1 christos ASSERT(0 == uv_pipe_init(loop, (uv_pipe_t*) &pc->peer_handle, 1)); 123 1.1 christos else 124 1.1 christos ASSERT(0); 125 1.1 christos 126 1.1 christos ASSERT(0 == uv_accept(ipc_pipe, (uv_stream_t*) &pc->peer_handle)); 127 1.1 christos ASSERT(0 == uv_write2(&pc->write_req, 128 1.1 christos (uv_stream_t*) &pc->peer_handle, 129 1.1 christos &buf, 130 1.1 christos 1, 131 1.1 christos (uv_stream_t*) &sc->server_handle, 132 1.1 christos ipc_write_cb)); 133 1.1 christos 134 1.1 christos if (--sc->num_connects == 0) 135 1.1 christos uv_close((uv_handle_t*) ipc_pipe, NULL); 136 1.1 christos } 137 1.1 christos 138 1.1 christos 139 1.1 christos static void ipc_write_cb(uv_write_t* req, int status) { 140 1.1 christos struct ipc_peer_ctx* ctx; 141 1.1 christos ctx = container_of(req, struct ipc_peer_ctx, write_req); 142 1.1 christos uv_close((uv_handle_t*) &ctx->peer_handle, ipc_close_cb); 143 1.1 christos } 144 1.1 christos 145 1.1 christos 146 1.1 christos static void ipc_close_cb(uv_handle_t* handle) { 147 1.1 christos struct ipc_peer_ctx* ctx; 148 1.1 christos ctx = container_of(handle, struct ipc_peer_ctx, peer_handle); 149 1.1 christos free(ctx); 150 1.1 christos } 151 1.1 christos 152 1.1 christos 153 1.1 christos static void ipc_connect_cb(uv_connect_t* req, int status) { 154 1.1 christos struct ipc_client_ctx* ctx; 155 1.1 christos ctx = container_of(req, struct ipc_client_ctx, connect_req); 156 1.1 christos ASSERT(0 == status); 157 1.1 christos ASSERT(0 == uv_read_start((uv_stream_t*) &ctx->ipc_pipe, 158 1.1 christos ipc_alloc_cb, 159 1.1 christos ipc_read_cb)); 160 1.1 christos } 161 1.1 christos 162 1.1 christos 163 1.1 christos static void ipc_alloc_cb(uv_handle_t* handle, 164 1.1 christos size_t suggested_size, 165 1.1 christos uv_buf_t* buf) { 166 1.1 christos struct ipc_client_ctx* ctx; 167 1.1 christos ctx = container_of(handle, struct ipc_client_ctx, ipc_pipe); 168 1.1 christos buf->base = ctx->scratch; 169 1.1 christos buf->len = sizeof(ctx->scratch); 170 1.1 christos } 171 1.1 christos 172 1.1 christos 173 1.1 christos static void ipc_read_cb(uv_stream_t* handle, 174 1.1 christos ssize_t nread, 175 1.1 christos const uv_buf_t* buf) { 176 1.1 christos struct ipc_client_ctx* ctx; 177 1.1 christos uv_loop_t* loop; 178 1.1 christos uv_handle_type type; 179 1.1 christos uv_pipe_t* ipc_pipe; 180 1.1 christos 181 1.1 christos ipc_pipe = (uv_pipe_t*) handle; 182 1.1 christos ctx = container_of(ipc_pipe, struct ipc_client_ctx, ipc_pipe); 183 1.1 christos loop = ipc_pipe->loop; 184 1.1 christos 185 1.1 christos ASSERT(1 == uv_pipe_pending_count(ipc_pipe)); 186 1.1 christos type = uv_pipe_pending_type(ipc_pipe); 187 1.1 christos if (type == UV_TCP) 188 1.1 christos ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) ctx->server_handle)); 189 1.1 christos else if (type == UV_NAMED_PIPE) 190 1.1 christos ASSERT(0 == uv_pipe_init(loop, (uv_pipe_t*) ctx->server_handle, 0)); 191 1.1 christos else 192 1.1 christos ASSERT(0); 193 1.1 christos 194 1.1 christos ASSERT(0 == uv_accept(handle, ctx->server_handle)); 195 1.1 christos uv_close((uv_handle_t*) &ctx->ipc_pipe, NULL); 196 1.1 christos } 197 1.1 christos 198 1.1 christos 199 1.1 christos /* Set up an IPC pipe server that hands out listen sockets to the worker 200 1.1 christos * threads. It's kind of cumbersome for such a simple operation, maybe we 201 1.1 christos * should revive uv_import() and uv_export(). 202 1.1 christos */ 203 1.1 christos static void send_listen_handles(uv_handle_type type, 204 1.1 christos unsigned int num_servers, 205 1.1 christos struct server_ctx* servers) { 206 1.1 christos struct ipc_server_ctx ctx; 207 1.1 christos uv_loop_t* loop; 208 1.1 christos unsigned int i; 209 1.1 christos 210 1.1 christos loop = uv_default_loop(); 211 1.1 christos ctx.num_connects = num_servers; 212 1.1 christos 213 1.1 christos if (type == UV_TCP) { 214 1.1 christos ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) &ctx.server_handle)); 215 1.1 christos ASSERT(0 == uv_tcp_bind((uv_tcp_t*) &ctx.server_handle, 216 1.1 christos (const struct sockaddr*) &listen_addr, 217 1.1 christos 0)); 218 1.1 christos } 219 1.1 christos else 220 1.1 christos ASSERT(0); 221 1.1 christos /* We need to initialize this pipe with ipc=0 - this is not a uv_pipe we'll 222 1.1 christos * be sending handles over, it's just for listening for new connections. 223 1.1 christos * If we accept a connection then the connected pipe must be initialized 224 1.1 christos * with ipc=1. 225 1.1 christos */ 226 1.1 christos ASSERT(0 == uv_pipe_init(loop, &ctx.ipc_pipe, 0)); 227 1.1 christos ASSERT(0 == uv_pipe_bind(&ctx.ipc_pipe, IPC_PIPE_NAME)); 228 1.1 christos ASSERT(0 == uv_listen((uv_stream_t*) &ctx.ipc_pipe, 128, ipc_connection_cb)); 229 1.1 christos 230 1.1 christos for (i = 0; i < num_servers; i++) 231 1.1 christos uv_sem_post(&servers[i].semaphore); 232 1.1 christos 233 1.1 christos ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); 234 1.1 christos uv_close((uv_handle_t*) &ctx.server_handle, NULL); 235 1.1 christos ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); 236 1.1 christos 237 1.1 christos for (i = 0; i < num_servers; i++) 238 1.1 christos uv_sem_wait(&servers[i].semaphore); 239 1.1 christos } 240 1.1 christos 241 1.1 christos 242 1.1 christos static void get_listen_handle(uv_loop_t* loop, uv_stream_t* server_handle) { 243 1.1 christos struct ipc_client_ctx ctx; 244 1.1 christos 245 1.1 christos ctx.server_handle = server_handle; 246 1.1 christos ctx.server_handle->data = "server handle"; 247 1.1 christos 248 1.1 christos ASSERT(0 == uv_pipe_init(loop, &ctx.ipc_pipe, 1)); 249 1.1 christos uv_pipe_connect(&ctx.connect_req, 250 1.1 christos &ctx.ipc_pipe, 251 1.1 christos IPC_PIPE_NAME, 252 1.1 christos ipc_connect_cb); 253 1.1 christos ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); 254 1.1 christos } 255 1.1 christos 256 1.1 christos 257 1.1 christos static void server_cb(void *arg) { 258 1.1 christos struct server_ctx *ctx; 259 1.1 christos uv_loop_t loop; 260 1.1 christos 261 1.1 christos ctx = arg; 262 1.1 christos ASSERT(0 == uv_loop_init(&loop)); 263 1.1 christos 264 1.1 christos ASSERT(0 == uv_async_init(&loop, &ctx->async_handle, sv_async_cb)); 265 1.1 christos uv_unref((uv_handle_t*) &ctx->async_handle); 266 1.1 christos 267 1.1 christos /* Wait until the main thread is ready. */ 268 1.1 christos uv_sem_wait(&ctx->semaphore); 269 1.1 christos get_listen_handle(&loop, (uv_stream_t*) &ctx->server_handle); 270 1.1 christos uv_sem_post(&ctx->semaphore); 271 1.1 christos 272 1.1 christos /* Now start the actual benchmark. */ 273 1.1 christos ASSERT(0 == uv_listen((uv_stream_t*) &ctx->server_handle, 274 1.1 christos 128, 275 1.1 christos sv_connection_cb)); 276 1.1 christos ASSERT(0 == uv_run(&loop, UV_RUN_DEFAULT)); 277 1.1 christos 278 1.1 christos uv_loop_close(&loop); 279 1.1 christos } 280 1.1 christos 281 1.1 christos 282 1.1 christos static void sv_async_cb(uv_async_t* handle) { 283 1.1 christos struct server_ctx* ctx; 284 1.1 christos ctx = container_of(handle, struct server_ctx, async_handle); 285 1.1 christos uv_close((uv_handle_t*) &ctx->server_handle, NULL); 286 1.1 christos uv_close((uv_handle_t*) &ctx->async_handle, NULL); 287 1.1 christos } 288 1.1 christos 289 1.1 christos 290 1.1 christos static void sv_connection_cb(uv_stream_t* server_handle, int status) { 291 1.1 christos handle_storage_t* storage; 292 1.1 christos struct server_ctx* ctx; 293 1.1 christos 294 1.1 christos ctx = container_of(server_handle, struct server_ctx, server_handle); 295 1.1 christos ASSERT(status == 0); 296 1.1 christos 297 1.1 christos storage = malloc(sizeof(*storage)); 298 1.1.1.2 christos ASSERT_NOT_NULL(storage); 299 1.1 christos 300 1.1 christos if (server_handle->type == UV_TCP) 301 1.1 christos ASSERT(0 == uv_tcp_init(server_handle->loop, (uv_tcp_t*) storage)); 302 1.1 christos else if (server_handle->type == UV_NAMED_PIPE) 303 1.1 christos ASSERT(0 == uv_pipe_init(server_handle->loop, (uv_pipe_t*) storage, 0)); 304 1.1 christos else 305 1.1 christos ASSERT(0); 306 1.1 christos 307 1.1 christos ASSERT(0 == uv_accept(server_handle, (uv_stream_t*) storage)); 308 1.1 christos ASSERT(0 == uv_read_start((uv_stream_t*) storage, sv_alloc_cb, sv_read_cb)); 309 1.1 christos ctx->num_connects++; 310 1.1 christos } 311 1.1 christos 312 1.1 christos 313 1.1 christos static void sv_alloc_cb(uv_handle_t* handle, 314 1.1 christos size_t suggested_size, 315 1.1 christos uv_buf_t* buf) { 316 1.1 christos static char slab[32]; 317 1.1 christos buf->base = slab; 318 1.1 christos buf->len = sizeof(slab); 319 1.1 christos } 320 1.1 christos 321 1.1 christos 322 1.1 christos static void sv_read_cb(uv_stream_t* handle, 323 1.1 christos ssize_t nread, 324 1.1 christos const uv_buf_t* buf) { 325 1.1 christos ASSERT(nread == UV_EOF); 326 1.1 christos uv_close((uv_handle_t*) handle, (uv_close_cb) free); 327 1.1 christos } 328 1.1 christos 329 1.1 christos 330 1.1 christos static void cl_connect_cb(uv_connect_t* req, int status) { 331 1.1 christos struct client_ctx* ctx = container_of(req, struct client_ctx, connect_req); 332 1.1 christos uv_idle_start(&ctx->idle_handle, cl_idle_cb); 333 1.1 christos ASSERT(0 == status); 334 1.1 christos } 335 1.1 christos 336 1.1 christos 337 1.1 christos static void cl_idle_cb(uv_idle_t* handle) { 338 1.1 christos struct client_ctx* ctx = container_of(handle, struct client_ctx, idle_handle); 339 1.1 christos uv_close((uv_handle_t*) &ctx->client_handle, cl_close_cb); 340 1.1 christos uv_idle_stop(&ctx->idle_handle); 341 1.1 christos } 342 1.1 christos 343 1.1 christos 344 1.1 christos static void cl_close_cb(uv_handle_t* handle) { 345 1.1 christos struct client_ctx* ctx; 346 1.1 christos 347 1.1 christos ctx = container_of(handle, struct client_ctx, client_handle); 348 1.1 christos 349 1.1 christos if (--ctx->num_connects == 0) { 350 1.1 christos uv_close((uv_handle_t*) &ctx->idle_handle, NULL); 351 1.1 christos return; 352 1.1 christos } 353 1.1 christos 354 1.1 christos ASSERT(0 == uv_tcp_init(handle->loop, (uv_tcp_t*) &ctx->client_handle)); 355 1.1 christos ASSERT(0 == uv_tcp_connect(&ctx->connect_req, 356 1.1 christos (uv_tcp_t*) &ctx->client_handle, 357 1.1 christos (const struct sockaddr*) &listen_addr, 358 1.1 christos cl_connect_cb)); 359 1.1 christos } 360 1.1 christos 361 1.1 christos 362 1.1 christos static int test_tcp(unsigned int num_servers, unsigned int num_clients) { 363 1.1 christos struct server_ctx* servers; 364 1.1 christos struct client_ctx* clients; 365 1.1 christos uv_loop_t* loop; 366 1.1 christos uv_tcp_t* handle; 367 1.1 christos unsigned int i; 368 1.1 christos double time; 369 1.1 christos 370 1.1 christos ASSERT(0 == uv_ip4_addr("127.0.0.1", TEST_PORT, &listen_addr)); 371 1.1 christos loop = uv_default_loop(); 372 1.1 christos 373 1.1 christos servers = calloc(num_servers, sizeof(servers[0])); 374 1.1 christos clients = calloc(num_clients, sizeof(clients[0])); 375 1.1.1.2 christos ASSERT_NOT_NULL(servers); 376 1.1.1.2 christos ASSERT_NOT_NULL(clients); 377 1.1 christos 378 1.1 christos /* We're making the assumption here that from the perspective of the 379 1.1 christos * OS scheduler, threads are functionally equivalent to and interchangeable 380 1.1 christos * with full-blown processes. 381 1.1 christos */ 382 1.1 christos for (i = 0; i < num_servers; i++) { 383 1.1 christos struct server_ctx* ctx = servers + i; 384 1.1 christos ASSERT(0 == uv_sem_init(&ctx->semaphore, 0)); 385 1.1 christos ASSERT(0 == uv_thread_create(&ctx->thread_id, server_cb, ctx)); 386 1.1 christos } 387 1.1 christos 388 1.1 christos send_listen_handles(UV_TCP, num_servers, servers); 389 1.1 christos 390 1.1 christos for (i = 0; i < num_clients; i++) { 391 1.1 christos struct client_ctx* ctx = clients + i; 392 1.1 christos ctx->num_connects = NUM_CONNECTS / num_clients; 393 1.1 christos handle = (uv_tcp_t*) &ctx->client_handle; 394 1.1 christos handle->data = "client handle"; 395 1.1 christos ASSERT(0 == uv_tcp_init(loop, handle)); 396 1.1 christos ASSERT(0 == uv_tcp_connect(&ctx->connect_req, 397 1.1 christos handle, 398 1.1 christos (const struct sockaddr*) &listen_addr, 399 1.1 christos cl_connect_cb)); 400 1.1 christos ASSERT(0 == uv_idle_init(loop, &ctx->idle_handle)); 401 1.1 christos } 402 1.1 christos 403 1.1 christos { 404 1.1 christos uint64_t t = uv_hrtime(); 405 1.1 christos ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT)); 406 1.1 christos t = uv_hrtime() - t; 407 1.1 christos time = t / 1e9; 408 1.1 christos } 409 1.1 christos 410 1.1 christos for (i = 0; i < num_servers; i++) { 411 1.1 christos struct server_ctx* ctx = servers + i; 412 1.1 christos uv_async_send(&ctx->async_handle); 413 1.1 christos ASSERT(0 == uv_thread_join(&ctx->thread_id)); 414 1.1 christos uv_sem_destroy(&ctx->semaphore); 415 1.1 christos } 416 1.1 christos 417 1.1 christos printf("accept%u: %.0f accepts/sec (%u total)\n", 418 1.1 christos num_servers, 419 1.1 christos NUM_CONNECTS / time, 420 1.1 christos NUM_CONNECTS); 421 1.1 christos 422 1.1 christos for (i = 0; i < num_servers; i++) { 423 1.1 christos struct server_ctx* ctx = servers + i; 424 1.1 christos printf(" thread #%u: %.0f accepts/sec (%u total, %.1f%%)\n", 425 1.1 christos i, 426 1.1 christos ctx->num_connects / time, 427 1.1 christos ctx->num_connects, 428 1.1 christos ctx->num_connects * 100.0 / NUM_CONNECTS); 429 1.1 christos } 430 1.1 christos 431 1.1 christos free(clients); 432 1.1 christos free(servers); 433 1.1 christos 434 1.1 christos MAKE_VALGRIND_HAPPY(); 435 1.1 christos return 0; 436 1.1 christos } 437 1.1 christos 438 1.1 christos 439 1.1 christos BENCHMARK_IMPL(tcp_multi_accept2) { 440 1.1 christos return test_tcp(2, 40); 441 1.1 christos } 442 1.1 christos 443 1.1 christos 444 1.1 christos BENCHMARK_IMPL(tcp_multi_accept4) { 445 1.1 christos return test_tcp(4, 40); 446 1.1 christos } 447 1.1 christos 448 1.1 christos 449 1.1 christos BENCHMARK_IMPL(tcp_multi_accept8) { 450 1.1 christos return test_tcp(8, 40); 451 1.1 christos } 452