qemu/tests/test-coroutine.c
<<
>>
Prefs
   1/*
   2 * Coroutine tests
   3 *
   4 * Copyright IBM, Corp. 2011
   5 *
   6 * Authors:
   7 *  Stefan Hajnoczi    <stefanha@linux.vnet.ibm.com>
   8 *
   9 * This work is licensed under the terms of the GNU LGPL, version 2 or later.
  10 * See the COPYING.LIB file in the top-level directory.
  11 *
  12 */
  13
  14#include <glib.h>
  15#include "qemu/coroutine.h"
  16#include "qemu/coroutine_int.h"
  17
  18/*
  19 * Check that qemu_in_coroutine() works
  20 */
  21
  22static void coroutine_fn verify_in_coroutine(void *opaque)
  23{
  24    g_assert(qemu_in_coroutine());
  25}
  26
  27static void test_in_coroutine(void)
  28{
  29    Coroutine *coroutine;
  30
  31    g_assert(!qemu_in_coroutine());
  32
  33    coroutine = qemu_coroutine_create(verify_in_coroutine);
  34    qemu_coroutine_enter(coroutine, NULL);
  35}
  36
  37/*
  38 * Check that qemu_coroutine_self() works
  39 */
  40
  41static void coroutine_fn verify_self(void *opaque)
  42{
  43    g_assert(qemu_coroutine_self() == opaque);
  44}
  45
  46static void test_self(void)
  47{
  48    Coroutine *coroutine;
  49
  50    coroutine = qemu_coroutine_create(verify_self);
  51    qemu_coroutine_enter(coroutine, coroutine);
  52}
  53
  54/*
  55 * Check that coroutines may nest multiple levels
  56 */
  57
  58typedef struct {
  59    unsigned int n_enter;   /* num coroutines entered */
  60    unsigned int n_return;  /* num coroutines returned */
  61    unsigned int max;       /* maximum level of nesting */
  62} NestData;
  63
  64static void coroutine_fn nest(void *opaque)
  65{
  66    NestData *nd = opaque;
  67
  68    nd->n_enter++;
  69
  70    if (nd->n_enter < nd->max) {
  71        Coroutine *child;
  72
  73        child = qemu_coroutine_create(nest);
  74        qemu_coroutine_enter(child, nd);
  75    }
  76
  77    nd->n_return++;
  78}
  79
  80static void test_nesting(void)
  81{
  82    Coroutine *root;
  83    NestData nd = {
  84        .n_enter  = 0,
  85        .n_return = 0,
  86        .max      = 128,
  87    };
  88
  89    root = qemu_coroutine_create(nest);
  90    qemu_coroutine_enter(root, &nd);
  91
  92    /* Must enter and return from max nesting level */
  93    g_assert_cmpint(nd.n_enter, ==, nd.max);
  94    g_assert_cmpint(nd.n_return, ==, nd.max);
  95}
  96
  97/*
  98 * Check that yield/enter transfer control correctly
  99 */
 100
 101static void coroutine_fn yield_5_times(void *opaque)
 102{
 103    bool *done = opaque;
 104    int i;
 105
 106    for (i = 0; i < 5; i++) {
 107        qemu_coroutine_yield();
 108    }
 109    *done = true;
 110}
 111
 112static void test_yield(void)
 113{
 114    Coroutine *coroutine;
 115    bool done = false;
 116    int i = -1; /* one extra time to return from coroutine */
 117
 118    coroutine = qemu_coroutine_create(yield_5_times);
 119    while (!done) {
 120        qemu_coroutine_enter(coroutine, &done);
 121        i++;
 122    }
 123    g_assert_cmpint(i, ==, 5); /* coroutine must yield 5 times */
 124}
 125
 126static void coroutine_fn c2_fn(void *opaque)
 127{
 128    qemu_coroutine_yield();
 129}
 130
 131static void coroutine_fn c1_fn(void *opaque)
 132{
 133    Coroutine *c2 = opaque;
 134    qemu_coroutine_enter(c2, NULL);
 135}
 136
 137static void test_co_queue(void)
 138{
 139    Coroutine *c1;
 140    Coroutine *c2;
 141
 142    c1 = qemu_coroutine_create(c1_fn);
 143    c2 = qemu_coroutine_create(c2_fn);
 144
 145    qemu_coroutine_enter(c1, c2);
 146    memset(c1, 0xff, sizeof(Coroutine));
 147    qemu_coroutine_enter(c2, NULL);
 148}
 149
 150/*
 151 * Check that creation, enter, and return work
 152 */
 153
 154static void coroutine_fn set_and_exit(void *opaque)
 155{
 156    bool *done = opaque;
 157
 158    *done = true;
 159}
 160
 161static void test_lifecycle(void)
 162{
 163    Coroutine *coroutine;
 164    bool done = false;
 165
 166    /* Create, enter, and return from coroutine */
 167    coroutine = qemu_coroutine_create(set_and_exit);
 168    qemu_coroutine_enter(coroutine, &done);
 169    g_assert(done); /* expect done to be true (first time) */
 170
 171    /* Repeat to check that no state affects this test */
 172    done = false;
 173    coroutine = qemu_coroutine_create(set_and_exit);
 174    qemu_coroutine_enter(coroutine, &done);
 175    g_assert(done); /* expect done to be true (second time) */
 176}
 177
 178
 179#define RECORD_SIZE 10 /* Leave some room for expansion */
 180struct coroutine_position {
 181    int func;
 182    int state;
 183};
 184static struct coroutine_position records[RECORD_SIZE];
 185static unsigned record_pos;
 186
 187static void record_push(int func, int state)
 188{
 189    struct coroutine_position *cp = &records[record_pos++];
 190    g_assert_cmpint(record_pos, <, RECORD_SIZE);
 191    cp->func = func;
 192    cp->state = state;
 193}
 194
 195static void coroutine_fn co_order_test(void *opaque)
 196{
 197    record_push(2, 1);
 198    g_assert(qemu_in_coroutine());
 199    qemu_coroutine_yield();
 200    record_push(2, 2);
 201    g_assert(qemu_in_coroutine());
 202}
 203
 204static void do_order_test(void)
 205{
 206    Coroutine *co;
 207
 208    co = qemu_coroutine_create(co_order_test);
 209    record_push(1, 1);
 210    qemu_coroutine_enter(co, NULL);
 211    record_push(1, 2);
 212    g_assert(!qemu_in_coroutine());
 213    qemu_coroutine_enter(co, NULL);
 214    record_push(1, 3);
 215    g_assert(!qemu_in_coroutine());
 216}
 217
 218static void test_order(void)
 219{
 220    int i;
 221    const struct coroutine_position expected_pos[] = {
 222        {1, 1,}, {2, 1}, {1, 2}, {2, 2}, {1, 3}
 223    };
 224    do_order_test();
 225    g_assert_cmpint(record_pos, ==, 5);
 226    for (i = 0; i < record_pos; i++) {
 227        g_assert_cmpint(records[i].func , ==, expected_pos[i].func );
 228        g_assert_cmpint(records[i].state, ==, expected_pos[i].state);
 229    }
 230}
 231/*
 232 * Lifecycle benchmark
 233 */
 234
 235static void coroutine_fn empty_coroutine(void *opaque)
 236{
 237    /* Do nothing */
 238}
 239
 240static void perf_lifecycle(void)
 241{
 242    Coroutine *coroutine;
 243    unsigned int i, max;
 244    double duration;
 245
 246    max = 1000000;
 247
 248    g_test_timer_start();
 249    for (i = 0; i < max; i++) {
 250        coroutine = qemu_coroutine_create(empty_coroutine);
 251        qemu_coroutine_enter(coroutine, NULL);
 252    }
 253    duration = g_test_timer_elapsed();
 254
 255    g_test_message("Lifecycle %u iterations: %f s\n", max, duration);
 256}
 257
 258static void perf_nesting(void)
 259{
 260    unsigned int i, maxcycles, maxnesting;
 261    double duration;
 262
 263    maxcycles = 10000;
 264    maxnesting = 1000;
 265    Coroutine *root;
 266
 267    g_test_timer_start();
 268    for (i = 0; i < maxcycles; i++) {
 269        NestData nd = {
 270            .n_enter  = 0,
 271            .n_return = 0,
 272            .max      = maxnesting,
 273        };
 274        root = qemu_coroutine_create(nest);
 275        qemu_coroutine_enter(root, &nd);
 276    }
 277    duration = g_test_timer_elapsed();
 278
 279    g_test_message("Nesting %u iterations of %u depth each: %f s\n",
 280        maxcycles, maxnesting, duration);
 281}
 282
 283/*
 284 * Yield benchmark
 285 */
 286
 287static void coroutine_fn yield_loop(void *opaque)
 288{
 289    unsigned int *counter = opaque;
 290
 291    while ((*counter) > 0) {
 292        (*counter)--;
 293        qemu_coroutine_yield();
 294    }
 295}
 296
 297static void perf_yield(void)
 298{
 299    unsigned int i, maxcycles;
 300    double duration;
 301
 302    maxcycles = 100000000;
 303    i = maxcycles;
 304    Coroutine *coroutine = qemu_coroutine_create(yield_loop);
 305
 306    g_test_timer_start();
 307    while (i > 0) {
 308        qemu_coroutine_enter(coroutine, &i);
 309    }
 310    duration = g_test_timer_elapsed();
 311
 312    g_test_message("Yield %u iterations: %f s\n",
 313        maxcycles, duration);
 314}
 315
 316static __attribute__((noinline)) void dummy(unsigned *i)
 317{
 318    (*i)--;
 319}
 320
 321static void perf_baseline(void)
 322{
 323    unsigned int i, maxcycles;
 324    double duration;
 325
 326    maxcycles = 100000000;
 327    i = maxcycles;
 328
 329    g_test_timer_start();
 330    while (i > 0) {
 331        dummy(&i);
 332    }
 333    duration = g_test_timer_elapsed();
 334
 335    g_test_message("Function call %u iterations: %f s\n",
 336        maxcycles, duration);
 337}
 338
 339static __attribute__((noinline)) void perf_cost_func(void *opaque)
 340{
 341    qemu_coroutine_yield();
 342}
 343
 344static void perf_cost(void)
 345{
 346    const unsigned long maxcycles = 40000000;
 347    unsigned long i = 0;
 348    double duration;
 349    unsigned long ops;
 350    Coroutine *co;
 351
 352    g_test_timer_start();
 353    while (i++ < maxcycles) {
 354        co = qemu_coroutine_create(perf_cost_func);
 355        qemu_coroutine_enter(co, &i);
 356        qemu_coroutine_enter(co, NULL);
 357    }
 358    duration = g_test_timer_elapsed();
 359    ops = (long)(maxcycles / (duration * 1000));
 360
 361    g_test_message("Run operation %lu iterations %f s, %luK operations/s, "
 362                   "%luns per coroutine",
 363                   maxcycles,
 364                   duration, ops,
 365                   (unsigned long)(1000000000.0 * duration / maxcycles));
 366}
 367
 368int main(int argc, char **argv)
 369{
 370    g_test_init(&argc, &argv, NULL);
 371    g_test_add_func("/basic/co_queue", test_co_queue);
 372    g_test_add_func("/basic/lifecycle", test_lifecycle);
 373    g_test_add_func("/basic/yield", test_yield);
 374    g_test_add_func("/basic/nesting", test_nesting);
 375    g_test_add_func("/basic/self", test_self);
 376    g_test_add_func("/basic/in_coroutine", test_in_coroutine);
 377    g_test_add_func("/basic/order", test_order);
 378    if (g_test_perf()) {
 379        g_test_add_func("/perf/lifecycle", perf_lifecycle);
 380        g_test_add_func("/perf/nesting", perf_nesting);
 381        g_test_add_func("/perf/yield", perf_yield);
 382        g_test_add_func("/perf/function-call", perf_baseline);
 383        g_test_add_func("/perf/cost", perf_cost);
 384    }
 385    return g_test_run();
 386}
 387