bennyhuo / Bennyhuo

bennyhuo.vercel.app
8 stars 3 forks source link

渡劫 C++ 协程(2):实现一个序列生成器 | Bennyhuo #78

Open bennyhuo opened 2 years ago

bennyhuo commented 2 years ago

https://www.bennyhuo.com/2022/03/11/cpp-coroutines-02-generator/

序列生成器是一个非常经典的协程应用场景。

sailzeng commented 2 years ago

首先谢谢作者。很好的一个系列, 发现代码的位置在前言有附录。大家可以去那儿下载。 https://github.com/bennyhuo/CppCoroutines.git

bennyhuo commented 2 years ago

你看前言里面有写

--------------原始邮件-------------- 发件人:"sailzeng @.>; 发送时间:2022年6月29日(星期三) 凌晨0:15 收件人:"bennyhuo/Bennyhuo" @.>; 抄送:"Bennyhuo @.>;"Author @.>; 主题:Re: [bennyhuo/Bennyhuo] 渡劫 C++ 协程(2):实现一个序列生成器 | Bennyhuo (Issue #78)

首先谢谢作者。很好的一个系列, BTW:作者能把部分代码放入Github吗?现在有些代码是省略的。不太好看全。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

jr981008 commented 2 years ago

好文! 是不是没必要单独维护一个is_ready 协程初始状态 suspend_never initial_suspend(){ return {}; } 下一个值 int next(){ int value=h.promise().value; h.resume(); return value; }

判断函数 bool has_next(){ if(h.done())return false; else return true; }

就可以解决的停止问题 关键还是promiseType的生命周期交到外部管理,这里的is_ready管理有点混乱,不好理解,而且可以删除

bennyhuo commented 2 years ago

关键在于,调用hasnext的时候如果已经准备好值,是不能重复取下一个的,hasnext需要支持多次调用判断是否有下一个,但不能消费。你在next的时候直接 resume,那hasnext的功能就废了。而且resume完之后就可能没有值,也不能直接拿value返回。

--------------原始邮件-------------- 发件人:"jr981008 @.>; 发送时间:2022年6月29日(星期三) 晚上8:23 收件人:"bennyhuo/Bennyhuo" @.>; 抄送:"Bennyhuo @.>;"Author @.>; 主题:Re: [bennyhuo/Bennyhuo] 渡劫 C++ 协程(2):实现一个序列生成器 | Bennyhuo (Issue #78)

其实没必要单独维护一个is_ready 协程初始状态 suspend_never initial_suspend(){ return {}; } 下一个值 int next(){ h.resume(); return h.promise().value; }

判断函数 bool has_next(){ if(h.done())return false; else return true; }

就可以解决的停止问题 关键还是promiseType的生命周期交到外部管理,这里的is_ready管理有点混乱,不好理解,而且可以删除

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

jr981008 commented 2 years ago

云编码果然不靠谱☺,刚才写了个demo,和你说的一样,需要个终止判断

ChunelFeng commented 1 year ago

好文马克

L1nklink commented 1 year ago

作者您好, 我想问一下就斐波那契数列这个例子而言. 使用协程的"显而易见的好处"具体指什么? 我本地使用Celero库测了一下两种方法的运行效率, 协程的效率远低于传统类成员函数调用的模式. 以下是我的例子和结果

// Fibonacci
Generator fibonacci() {
    co_yield 0;
    co_yield 1;
    int a = 0, b = 1;
    while (true) {
        co_yield a + b;
        b = a + b;
        a = b - a;
    }
}

void SequenceTest_Fibonacci(int times) {
    auto gen = fibonacci();
    for (int i = 0; i < times; ++i) {
        gen.next();
    }
}

// classical
class Fibonacci {
 public:
    int next() {
        // 初值不符合整体的规律,需要单独处理
        if (a == -1) {
            a = 0;
            b = 1;
            return 0;
        }
        int next = b;
        b        = a + b;
        a        = b - a;
        return next;
    }

 private:
    int a = -1;
    int b = 0;
};

void Classical_Fibonacci(int times) {
    auto classical = Fibonacci();
    for (int i = 0; i < times; ++i) {
        classical.next();
    }
}

BASELINE(Fibonacci, BaselineClassical, 10, 10000) { Classical_Fibonacci(100000); }
BENCHMARK(Fibonacci, CoroutineSequenceGenerator, 10, 10000) { SequenceTest_Fibonacci(100000); }

// result

Celero
Timer resolution: 0.001000 us
|     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  |   RAM (bytes)   | 
|:--------------:|:---------------:|:---------------:|:---------------:|:---------------:|:---------------:|:---------------:|:---------------:|:---------------:|
|Fibonacci       | BaselineClassic |            Null |              10 |           10000 |         1.00000 |       374.37590 |         2671.11 |         8679424 | 
|Fibonacci       | CoroutineSequen |            Null |              10 |           10000 |         7.85000 |      2938.84920 |          340.27 |         8679424 | 
Completed in 00:06:05.465433
bennyhuo commented 1 year ago

@L1nklink 作者您好, 我想问一下就斐波那契数列这个例子而言. 使用协程的"显而易见的好处"具体指什么? 我本地使用Celero库测了一下两种方法的运行效率, 协程的效率远低于传统类成员函数调用的模式. 以下是我的例子和结果

// Fibonacci
Generator fibonacci() {
    co_yield 0;
    co_yield 1;
    int a = 0, b = 1;
    while (true) {
        co_yield a + b;
        b = a + b;
        a = b - a;
    }
}

void SequenceTest_Fibonacci(int times) {
    auto gen = fibonacci();
    for (int i = 0; i < times; ++i) {
        gen.next();
    }
}

// classical
class Fibonacci {
 public:
    int next() {
        // 初值不符合整体的规律,需要单独处理
        if (a == -1) {
            a = 0;
            b = 1;
            return 0;
        }
        int next = b;
        b        = a + b;
        a        = b - a;
        return next;
    }

 private:
    int a = -1;
    int b = 0;
};

void Classical_Fibonacci(int times) {
    auto classical = Fibonacci();
    for (int i = 0; i < times; ++i) {
        classical.next();
    }
}

BASELINE(Fibonacci, BaselineClassical, 10, 10000) { Classical_Fibonacci(100000); }
BENCHMARK(Fibonacci, CoroutineSequenceGenerator, 10, 10000) { SequenceTest_Fibonacci(100000); }

// result

Celero
Timer resolution: 0.001000 us
|     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  |   RAM (bytes)   | 
|:--------------:|:---------------:|:---------------:|:---------------:|:---------------:|:---------------:|:---------------:|:---------------:|:---------------:|
|Fibonacci       | BaselineClassic |            Null |              10 |           10000 |         1.00000 |       374.37590 |         2671.11 |         8679424 | 
|Fibonacci       | CoroutineSequen |            Null |              10 |           10000 |         7.85000 |      2938.84920 |          340.27 |         8679424 | 
Completed in 00:06:05.465433

主要是可读性上的提升。如果考虑性能,得看使用场景,直接求值的话肯定是调用效率更高,因为协程为了支持“挂起”,会有一些开销。当然,这个也看编译器自身的优化了。

L1nklink commented 1 year ago

@bennyhuo

@L1nklink

主要是可读性上的提升。如果考虑性能,得看使用场景,直接求值的话肯定是调用效率更高,因为协程为了支持“挂起”,会有一些开销。当然,这个也看编译器自身的优化了。

感谢回复, 我的理解应该也是受使用场景的影响很大. 再次感谢这样的好文章.

bennyhuo commented 1 year ago

@bennyhuo

@L1nklink

主要是可读性上的提升。如果考虑性能,得看使用场景,直接求值的话肯定是调用效率更高,因为协程为了支持“挂起”,会有一些开销。当然,这个也看编译器自身的优化了。

感谢回复, 我的理解应该也是受使用场景的影响很大. 再次感谢这样的好文章.

感谢支持~ 这一系列文章也可以在专栏中阅读哈,体验会更好一些:https://www.bennyhuo.com/book/cpp-coroutines/

Lw-Cui commented 1 year ago

我注意到有一点点笔误

  explicit Generator(std::coroutine_handle<promise_type> handle) noexcept
      : handle(handle) {}

感谢好文!

bennyhuo commented 1 year ago

我注意到有一点点笔误

  explicit Generator(std::coroutine_handle<promise_type> handle) noexcept
      : handle(handle) {}

感谢好文!

笔误具体是指?

Lw-Cui commented 1 year ago

我注意到有一点点笔误

  explicit Generator(std::coroutine_handle<promise_type> handle) noexcept
      : handle(handle) {}

感谢好文!

笔误具体是指?

不好意思,我理解错了!我错误地以为 handle(handle) 有问题