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