请教大家一个架构的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wisefree
V2EX    C++

请教大家一个架构的问题

  •  
  •   wisefree 2024-04-02 22:09:08 +08:00 3274 次点击
    这是一个创建于 555 天前的主题,其中的信息可能已经有所发展或是发生改变。

    假设有 3 个模块(可能更多),为了提高效率,每个模块一个线程,并绑定到特定 CPU 核心。

    1. 每个模块处理特定的数据,数据长度是已知的
    2. 前一个模块的输出,是后一个模块的输出。比如模块 1 输出,是模块 2 输入;模块 2 输出,是模块 3 输入

    当初想法是,模块 1 、模块 2 、模块 3 ,分别在不同线程,则通过也给消息中间件如 zeromq 通信,数据也完全通过消息中间件传输。

    现在有一个想法,在模块 1 、模块 2 、模块 3 中,线程启动前,分配好内存空间,消息中间件给各模块传输一个内存针和数据长度。

    1. 模块 1 中先建立 10 个内存块,处理完毕后,存入第一块内存,然后通知模块 2 ,模块 1 继续处理数据
    2. 模块 2 中也先建立 10 内存块,收到通知后,读取模块 1 的第一块内存地址和数据长度,处理完毕后,存入模块 2 的第一块内存,通知模块 3 ,模块 2 继续处理数据
    3. 模块 3 中也建立 10 块内存块,收到通知后,读取模块 2 的第一块内存地址和数据长度,处理完毕后,存入模块 3 的第一块内存,模块 3 继续处理数据
    4. 依次类推

    其中由于模块 1 、模块 2 、模块 3 ,读写并不是同一块内存,所以也不需要加锁等。因为各模块的性能要求很高,在线程中在堆上分配内存都被认为是耗时操作

    不知道这种传递内存地址的方式,是不是合适的?有没有更加高效的架构设计呢?

    22 条回复    2024-04-03 19:36:55 +08:00
    securityCoding
        1
    securityCoding  
       2024-04-02 22:13:43 +08:00 via Android   1
    不了解你的业务场景,通用后台业务来说这不就是 Kafka 三个 comsumer 么
    Inn0Vat10n
        2
    Inn0Vat10n  
       2024-04-02 22:16:36 +08:00   1
    数据源是啥样的呢,如果数据源本身是可拆的,输入数据拆成 n 份,分发到 k 个线程里,每个线程内部模块 1,2,3 串行在同一片内存上操作应该效率会更高
    wisefree
        3
    wisefree  
    OP
       2024-04-02 22:33:49 +08:00 via Android
    @securityCoding 嗯,流程上可以,只是为了追求极致的效率,所以在想通过指针操作
    wisefree
        4
    wisefree  
    OP
       2024-04-02 22:36:49 +08:00 via Android
    @Inn0Vat10n 数据源不能拆分,它是按时间先后外部传输过来的,模块一先接收
    Zhuzhuchenyan
        5
    Zhuzhuchenyan  
       2024-04-02 22:40:49 +08:00   1
    这个思路整体没有问题,给我的感觉就是一个环形的 buffer 设计,不过我想到一个场景。

    假设模块 1 已经完成了 10 份工作并通知模块 2 ,此时不知为何模块 2 还在处理第 1 份工作,此时模块 1 需要一种方式知道第 1 份内存还在被使用从而避免造成多线程竞争问题。

    如果有一个高性能的跨线程内存池的话,可以简化这个问题,前提是可以容忍内存池带来的开销。

    模块 1 借了以后,处理完把内存的所有权给模块 2 ,模块 2 读了以后把所有权还给内存池,以此类推。
    cI137
        6
    cI137  
       2024-04-02 23:10:54 +08:00 via iPhone   1
    actor 模型可以吗,云风的 skynet 框架,或者 go 的管道
    liberize
        7
    liberize  
       2024-04-02 23:22:34 +08:00 via Android   1
    这不就是环形缓冲区(ring buffer)吗,有无锁版本,实际也相当于一个容量固定的队列。
    leonshaw
        8
    leonshaw  
       2024-04-02 23:23:16 +08:00 via Android   1
    Ring buffer 基本上就是最高效的了,有不少库。自己实现也不难,主要注意内存屏障的使用和队满/空时的处理。
    codegenerator
        9
    codegenerator  
       2024-04-02 23:26:22 +08:00   2
    看着非常糟糕
    1. 为什么不同模块不同 cpu ?你是数据处理,数据尽量放在一个核心上明显性能高很多
    2. 多线程数据同步有很多方法,zeromq 是来搞笑的吗?
    3. 模块 1 不知道模块 2 什么时候处理完,有可能把没处理完的数据覆盖了
    4. 每个模块处理速度不确定,你很难假定每个模块速度

    本人资深架构师,可以付费提供优质服务
    liberize
        10
    liberize  
       2024-04-02 23:28:35 +08:00 via Android   1
    楼上说的问题很好解决,一般环形缓冲区队首和队尾之间会留一个空位,这样空和满是可以区分的。
    youngxhui838
        11
    youngxhui838  
       2024-04-03 01:19:08 +08:00 via Android   1
    可以看看 go pipeline 这种设计,用管道进行通信。https://go.dev/blog/pipelines
    wisefree
        12
    wisefree  
    OP
       2024-04-03 08:40:13 +08:00 via Android
    @Zhuzhuchenyan 是这样的,如果模块 1 的处理速度很快,模块 2 比较慢,形成了一个速度差的话,模块 1 迟早会把内存块用完,之前的设计就有漏洞。
    wisefree
        13
    wisefree  
    OP
       2024-04-03 08:40:52 +08:00 via Android
    @cI137 没试过 skynet 和 go ,目前在用 c++
    exch4nge
        14
    exch4nge  
       2024-04-03 08:46:17 +08:00 via iPhone   1
    绑核了就 spin 查消息吧,用什么 zeromq ,io 唤醒得有多慢。
    单个模块一个线程/核心?这么设计会更快么?不会有某个模块处理慢么,不加点线程/核心?
    切换线程可能把 cache 都丢了,有些情况下还不如一个线程跑完整个处理
    exch4nge
        15
    exch4nge  
       2024-04-03 08:58:22 +08:00 via iPhone   1
    建议先用一般思路实现一版本,然后再做这种设计,比对性能。性能这东西基本都得靠实践。
    一般思路:找个线程池实现,如 folly 的 cpu thread pool ,内存分配用 jemalloc 之类的
    wisefree
        16
    wisefree  
    OP
       2024-04-03 09:39:11 +08:00 via Android
    @liberize 其实我之前也搜了下环形缓冲,好像和提前分配内存,这个差不不大,但是环形缓冲确实更加方便
    wisefree
        17
    wisefree  
    OP
       2024-04-03 09:39:32 +08:00 via Android
    @youngxhui838 好的,我去看看
    wisefree
        18
    wisefree  
    OP
       2024-04-03 09:47:33 +08:00 via Android
    @liberize 是的,多谢啦,留空这个建议很有用
    wisefree
        19
    wisefree  
    OP
       2024-04-03 09:49:45 +08:00 via Android
    @exch4nge 好的,多谢建议哈
    cnbatch
        20
    cnbatch  
       2024-04-03 16:19:09 +08:00   1
    “在线程中在堆上分配内存都被认为是耗时操作”

    这个要视乎 libc 或 libc++而定,有些 libc 会把用过、free 掉的内存为该程序暂留着,不会那么快交还给 OS 。后续该程序再申请堆内存时就从暂留资源直接分配,耗时不会很高。
    cnbatch
        21
    cnbatch  
       2024-04-03 19:01:14 +08:00   1
    忘了一个,如果 OP 的编译器支持 C++17 ,可以考虑下 std::pmr
    wisefree
        22
    wisefree  
    OP
       2024-04-03 19:36:55 +08:00
    @cnbatch 多谢,我去学习这个功能
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     854 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 21:41 PVG 05:41 LAX 14:41 JFK 17:41
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86