编程文汇

longjmp和win10原生协程切换速度对比

先上结果

QQ%E5%9B%BE%E7%89%8720191031143914
可以看到
boost fcontext每秒可以进行4700万次上下文切换,
winfiber每秒1400万次
longjmp300万次。

都是单线程,都能把单个cpu内核负载跑满,基本上是公平的。

测试代码

  1. longjmp
        jmp_buf ctx;
        ilong start = now();
        printf("%lld\n", start);
        iint N = 3000000;
        for (iint i = 0; i < N; i++ ,r++)
        {
            if (!ezcr_setjmp_(&ctx)) {
                ezcr_longjmp_(&ctx);
            }
        }
        double ret = N * 1.0 / (now() - start) * 1000;
        printf("longjmp rps %f\n", ret);
  1. winfiber ,网上抄的
#define COROUTINE_DEAD     0
#define COROUTINE_READY    1
#define COROUTINE_RUNNING  2
#define COROUTINE_SUSPEND  3

    typedef struct schedule schedule;
    typedef void(*coroutine_func)(schedule* s, void* ud);

    schedule* coroutine_open();
    void coroutine_close(schedule* s);
    int coroutine_new(schedule* s, coroutine_func*, void* ud);
    void coroutine_resume(schedule* s, int id);
    void coroutine_yield(schedule* s);
    int coroutine_status(schedule* s, int id);


    /* windows fiber版本的协程yield的时候直接切换到主协程(main),
    而不是swapcontext的切换到上次运行协程,但最后达到的结果却一样
    */

    // 默认容量
#define DEFAULT_CAP   8
// 堆栈大小
#define INIT_STACK    1048576 //(1024*1024)

    typedef struct schedule schedule;
    typedef struct coroutine coroutine;
    typedef struct coroutine_para coroutine_para;

    struct schedule
    {
        int  cap;     // 容量
        int  conums;
        int  curID;   // 当前协程ID
        LPVOID    main;
        coroutine** co;
    };

    struct coroutine
    {
        schedule* s;
        void* ud;
        int       status;
        LPVOID    ctx;
        coroutine_func func;
    };

    static int co_putin(schedule* s, coroutine* co)
    {
        if (s->conums >= s->cap)
        {
            int id = s->cap;
            s->co = realloc(s->co, sizeof(coroutine*) * s->cap * 2);
            memset(&s->co[s->cap], 0, sizeof(coroutine*) * s->cap);
            s->co[s->cap] = co;
            s->cap *= 2;
            ++s->conums;
            return id;
        }
        else
        {
            for (int i = 0; i < s->cap; i++)
            {
                int id = (i + s->conums) % s->cap;
                if (s->co[id] == NULL)
                {
                    s->co[id] = co;
                    ++s->conums;
                    return id;
                }
            }
        }
        assert(0);
        return -1;
    }

    static void co_delete(coroutine* co)
    {
        //If the currently running fiber calls DeleteFiber, its thread calls ExitThread and terminates.
        //However, if a currently running fiber is deleted by another fiber, the thread running the 
        //deleted fiber is likely to terminate abnormally because the fiber stack has been freed.
        DeleteFiber(co->ctx);
        free(co);
    }

    schedule* coroutine_open()
    {
        schedule* s = malloc(sizeof(schedule));
        s->cap = DEFAULT_CAP;
        s->conums = 0;
        s->curID = -1;
        s->co = malloc(sizeof(coroutine*) * s->cap);
        memset(s->co, 0, sizeof(coroutine*) * s->cap);
        s->main = ConvertThreadToFiberEx(NULL, FIBER_FLAG_FLOAT_SWITCH);
        return s;
    }

    void coroutine_close(schedule* s)
    {
        for (int i = 0; i < s->cap; i++)
        {
            coroutine* co = s->co[i];
            if (co) co_delete(co);
        }
        free(s->co);
        s->co = NULL;
        free(s);
    }

    void __stdcall coroutine_main(LPVOID lpParameter)
    {
        schedule* s = (schedule*)lpParameter;
        int id = s->curID;
        coroutine* co = s->co[id];

        (co->func)(s, co->ud);

        s->curID = -1;
        --s->conums;
        s->co[id] = NULL;
        //co_delete(co);

        SwitchToFiber(s->main);
    }

    int coroutine_new(schedule* s, coroutine_func* func, void* ud)
    {
        coroutine* co = malloc(sizeof(coroutine));
        co->s = s;
        co->status = COROUTINE_READY;
        co->func = func;
        co->ud = ud;
        int id = co_putin(s, co);
        co->ctx = CreateFiberEx(INIT_STACK, 0, FIBER_FLAG_FLOAT_SWITCH, coroutine_main, s);
        co->status = COROUTINE_READY;

        return id;
    }

    void coroutine_resume(schedule* s, int id)
    {
        assert(id >= 0 && id < s->cap);
        if (id < 0 || id >= s->cap) return;
        coroutine* co = s->co[id];
        if (co == NULL) return;
        switch (co->status)
        {
        case COROUTINE_READY:case COROUTINE_SUSPEND:
            co->status = COROUTINE_RUNNING;
            s->curID = id;
            SwitchToFiber(co->ctx);
            if (!s->co[id]) co_delete(co);
            break;
        default:
            assert(0);
            break;
        }
    }

    void coroutine_yield(schedule* s)
    {
        int id = s->curID;
        assert(id >= 0 && id < s->cap);
        if (id < 0) return;

        coroutine* co = s->co[id];
        co->status = COROUTINE_SUSPEND;
        s->curID = -1;

        SwitchToFiber(s->main);
    }

    int coroutine_status(schedule* s, int id)
    {
        assert(id >= 0 && id < s->cap);
        if (id < 0) return;
        if (s->co[id] == NULL) {
            return COROUTINE_DEAD;
        }
        return s->co[id]->status;
    }

    int coroutine_running(schedule* s)
    {
        return s->curID;
    }

    void test3(schedule* s, void* ud)
    {
        ilong start = now();
        iint N = 9000000;
        int* data = (int*)ud;
        for (int i = 0; i < N; i++)
        {
            //printf("test3 i=%d\n", i);
            coroutine_yield(s);
            //printf("yield co id = %d.\n", *data);
        }
        double ret = N * 1.0 / (now() - start) * 1000;
        printf("winfiber rps :%f\n", ret);
    }

void main() {
        schedule* s = coroutine_open();

        int a = 11;
        int id1 = coroutine_new(s, test3, &a);

        while (coroutine_status(s, id1) )
        {
            //printf("\nresume co id = %d.\n", id1);
            coroutine_resume(s, id1);
            //printf("resume co id = %d.\n", id2);
            //coroutine_resume(s, id2);
        }
}
  1. boost fcontext
iint spsize = 1024 * 8;
void *sp ;

void test_asmjmp() {
    sp = malloc(spsize);
    iint N = 50000000;
    ilong start = ezg_now();
    ilong a = 0;
    
    for (iint i = 0; i < N; i++) {
        a++;
        fcontext_t fctx = make_fcontext(sp, spsize, func_coro);
        //memset(sp, 0, sizeof(sp));
        //assert(fctx);
        jump_fcontext(fctx, 0);
    }
    printf(" ");
    ilong now = ezg_now();
    printf("%lld\n",now-start);
    double ret = N*1.0  / (1+ now - start)*1000 ;
    printf("test_asmjmp rps %lld , %lld \n", (ilong)ret,rr);
}