V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
Lime
V2EX  ›  分享创造

libcsp: 一个 10 倍于 Golang 的高性能 C 语言并发库

  •  
  •   Lime · 2020-04-07 09:10:07 +08:00 · 6007 次点击
    这是一个创建于 1676 天前的主题,其中的信息可能已经有所发展或是发生改变。

    特性:

    • 支持多核
    • 高性能调度器
    • 编译时栈大小静态分析
    • 受 Disruptor 启发的 Lock-free Channel
    • 支持 netpoll 和 timer

    Github: https://github.com/shiyanhui/libcsp

    文档: https://libcsp.com (英文, 中文文档找时间写一下)

    24 条回复    2020-04-09 12:46:50 +08:00
    reus
        1
    reus  
       2020-04-07 09:17:06 +08:00
    “10 倍”是怎么测试的?
    web 场景里,大并发下延迟的分布如何?
    Lime
        2
    Lime  
    OP
       2020-04-07 09:33:01 +08:00 via iPhone
    @reus 看文档
    mritd
        3
    mritd  
       2020-04-07 10:01:10 +08:00 via iPhone
    问题是为什么对比 Go 呢🤔
    Lime
        4
    Lime  
    OP
       2020-04-07 10:21:48 +08:00
    @mritd 因为两者都是基于 CSP 并发模型 而 C 语言并发库目前实现绝大部分都是单线程 不能利用多核
    reus
        5
    reus  
       2020-04-07 10:49:17 +08:00
    @Lime https://github.com/shiyanhui/libcsp/blob/master/benchmarks/sum.go 这里的写法不是 go 典型的做法,没人会无限量地开启 goroutine 的,更何况是这种生存期很短的 goroutine 。goroutine 的创建和调度都有开销。
    Lime
        6
    Lime  
    OP
       2020-04-07 11:14:07 +08:00
    @reus 对 Benchmark 的目的就是比较极端情况下的性能 libcsp 同样会不停的的创建 /调度 /运行 /销毁 process(对应 goroutine)
    wslzy007
        7
    wslzy007  
       2020-04-07 11:44:49 +08:00
    毕竟多核下 m:n 模式会有多线程,任务间线程安全如何保障?任务自己 lock/unlock ?频繁上下文切换会迅速抵消调度的高性能;
    Lime
        8
    Lime  
    OP
       2020-04-07 11:58:17 +08:00
    @wslzy007 这就涉及到具体调度实现了 可以看下 src/sched.c
    wslzy007
        9
    wslzy007  
       2020-04-07 12:04:10 +08:00   ❤️ 1
    看了,如果只是比较调度性能的话,建议找一个实现良好的 thread-pool 进行对比测试估计意义更大一些。
    Lime
        10
    Lime  
    OP
       2020-04-07 12:11:12 +08:00
    @wslzy007 赞 有时间我对 libcsp 和 thread-pool 做一下对比
    wslzy007
        11
    wslzy007  
       2020-04-07 12:18:43 +08:00
    事实上最后的性能差异更多的集中在两个方面:
    1 、调度策略(尽可能保证无上下文切换;兼顾快、慢任务,任务优先级考虑。。。)
    2 、lock-free queue 性能
    fakevam
        12
    fakevam  
       2020-04-07 14:26:18 +08:00
    1. mutex 的实现太暴力了,有些场景会很糟糕,怎么退化 mutex 是需要考虑的,退化 mutex 以后这个问题就复杂化了
    2. 调度优先级也过于暴力,考虑性能比较多,考虑公平性不够,可能出现调度饥渴,另外 task 的亲和性问题貌似没看到太多的处理,计算敏感的场景下,task 的亲和性还是很重要的
    3. 准备下配套的 gdb 调试脚本,否则切换协程以后压根没法调试,不是每个人都会去解析那一堆的上下文的
    4. 一个 epoll 大概率是不够的,epoll 在某些场景下性能退化很严重的,有些东西比较糟心

    抛开这些,代码质量还是不错的
    不过我还是不推荐玩这种东西,受限太严重了,需要性能的时候,一切都需要自己控制,不需要的时候,golang 的那套足够覆盖了
    Lime
        13
    Lime  
    OP
       2020-04-07 15:48:35 +08:00
    @fakevam 评价的很独到客观了 眼力很厉害 赞

    1. mutex 这个 避免不了 如果不用的话 可能会有更大的代价 实现的时候仅用在那些很大概率不会冲突的地方 后续会考虑看有没有添加 RWmutex 的必要

    2. 在"调度公平"这个问题上 简单来说 libcsp 维护两种队列 一个是全局队列 一个是本地队列 挂起的 process(上下文)放到本地队列末尾 每次从本地队列头部取新的要执行的 process libcsp 每隔一定固定次数就会去全局队列去取 所以"调度饥渴"问题不会出现 对于在全局队列的 processes 确实会有调度公平的问题 另外 task 亲和性目前实现也确实没有考虑

    3. 是的 这个后续会添加上

    4. 这个后续也会改进

    libcsp 还需要进化 比如支持更多架构(比如 arm)/支持 C++/支持 clang 编译器 /支持更多 OS 等
    baoyexi
        14
    baoyexi  
       2020-04-07 17:41:51 +08:00
    @fakevam 您好,关于退化 mutex 方面是否有经验可以分享一下,最近遇到了 mutex 占用资源的问题一直没有什么头绪。
    fakevam
        15
    fakevam  
       2020-04-07 17:57:59 +08:00   ❤️ 1
    @baoyexi
    mutex 这个东西不要想着去优化锁本身的性能
    1. 锁内部的代码,是否足够轻便和必要
    2. 锁冲突的路径是不是可以考虑转移,把锁转移到一个数据流更加冷的路径下去

    @Lime mutex 自己做的成本很高,这是我不推荐用协程的关键原因,因为没有人知道谁会怎么跑你在你的任务框架上,协程的锁理论需要做锁膨胀才是最优解

    调度饥渴就是调度公平问题,考虑一个很经典的场景,N 个人去 xxx 部门开证明,有 M 个步骤,每个步骤都需要串行,最后的结果就是第一个人处处优先,最后一个特别慢,明显这种情况下,最后一个人可能是很不满意的
    内核的 cfs 调度其实某些角度就是为了解决这个问题,epoll_wait 的 event,也可能导致类似的问题,我只开个头,你自己思考
    kingddc314
        16
    kingddc314  
       2020-04-07 17:59:33 +08:00
    赞一个
    fakevam
        17
    fakevam  
       2020-04-07 17:59:57 +08:00   ❤️ 1
    @baoyexi 补充一点,lock-free 对于 99.99%的代码来说是屠龙之术
    一把锁只有没有办法降低冲突了,才会考虑 lock-free,否则优先考虑怎么避免冲突
    waruqi
        18
    waruqi  
       2020-04-07 22:47:48 +08:00 via Android
    赞!可以考虑下用 xmake 来维护和跨平台编译哦
    Fu4ng
        19
    Fu4ng  
       2020-04-07 23:24:24 +08:00
    很赞的项目,已 start,打算这几天闲下来,仔细看下。
    Fu4ng
        20
    Fu4ng  
       2020-04-07 23:29:04 +08:00
    对了,您的“hero”项目的中文 readme 中的 bthub.io 连接似乎失效了
    baoyexi
        21
    baoyexi  
       2020-04-08 10:27:19 +08:00
    @fakevam 非常感谢您的分享,受益颇多。
    lfcyme
        22
    lfcyme  
       2020-04-08 12:33:04 +08:00 via Android
    感谢分享
    yulon
        23
    yulon  
       2020-04-08 23:58:45 +08:00
    你是不是对 goroutine 有什么误解,如果只是并行计算任务,直接用线程池就行了,一颗 CPU 的并行数几乎是死的,后面排队的是线程还是协程还是单纯的函数队列都只是内存大小的区别。

    协程是为了并发,把异步 I/O 封装成同步 I/O,性能差别出在休眠和唤醒上面,也就是把异步封装成同步的代价。

    异步 I/O 是为了将所有任务需要等待的地方放到一起等待,省去多余的等待,让原来需要等待的线程可以一直计算来利用 CPU 。

    结果你的实现其他等待的线程都需要空转,根本不做事确一直占着 CPU,完全本末倒置了。
    Lime
        24
    Lime  
    OP
       2020-04-09 12:46:50 +08:00
    @yulon 认真看下代码?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5170 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 03:47 · PVG 11:47 · LAX 19:47 · JFK 22:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.