[求助]请教一个 C++多线程的性能问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Ainokiseki
V2EX    C++

[求助]请教一个 C++多线程的性能问题

  •  
  •   Ainokiseki 1 天前 1176 次点击
    LZ 受命使用多线程提高某个程序的运行效率。原来程序的行为大概是 step0,step1,step2 之后回到 step0 ,循环若干次。LZ 把 step1 和 step2 的一部分流程并行化,开了三个后台线程做消费者,主线程把任务放在队列里面,消费者线程拿取之后运行,运行完毕后主线程继续。代码如下:

    ```
    std::vector<std::future<void>>futures;
    for(auto npg:m_NPGs){
    futures.emplace_back(ThreadPoolSingleton::getNPCInstance(threadNum.getValue()).enqueue([this,npg]() {
    npg->cycleStep1();
    }));
    }

    for(auto &k:futures){
    k.wait();
    }
    ```
    线程池代码如下:
    ```
    inline ThreadPool::ThreadPool(size_t threads)
    : stop(false)
    {
    for(size_t i = 0;i<threads;++i)
    workers.emplace_back(
    [this]
    {
    for(;;)
    {
    std::function<void()> task;

    {
    std::unique_lock<std::mutex> lock(this->queue_mutex);
    this->condition.wait(lock,
    [this]{ return this->stop || !this->tasks.empty(); });
    if(this->stop && this->tasks.empty())
    return;
    task = std::move(this->tasks.front());
    this->tasks.pop();
    }

    task();
    }
    }
    );
    }
    ```
    然而出乎楼主预料的是,使用多线程之后性能不增反降,进一步分析发现没有使用多线程优化的 step0 的用时相较之前有着明显增加,幅度达到了单线程时的两倍。火焰图上看,step0 期间调用的每个函数用时增长幅度大致相当。

    单线程时,用时分别为:t0=2s, t1=5.58s, t2=10s 多线程时,用时为:t0=3.97s t1=9.3s t2=10.4s

    楼主现在不理解的是,step0 看起来和我的多线程优化没有任何关系,在 step0 运行时,三个后台线程应该都在被 condition_variable 阻塞。为什么用时会增加呢?
    楼主运行环境为多人共用的 linux 服务器,100 多个空闲核心,开三个后台线程应该不会有什么影响。
    20 条回复    2025-11-13 09:47:12 +08:00
    AnroZ
        1
    AnroZ  
       1 天前
    step0 干了什么?代码没看到,没办法帮忙分析。
    单独开个三个后台线程没什么问题,但每个人都这么想会有问题,应该有资源分配限制吧?
    RinGress
        2
    RinGress  
       1 天前
    没代码不好说。
    能想到的是核间通信延迟、cache 命中降低,额外开销切换等方面因素。
    Ainokiseki
        3
    Ainokiseki  
    OP
       23 小时 56 分钟前
    @AnroZ 是这样,开三个后台线程就是说这个进程一共就四个线程,只有 step1 和 step2 的时候会往队列里放任务,然后等任务做完之后才会继续下一个 step ,所以 step0 的时候这三个后台线程应该是在等条变量,什么都不会做。所以 step0 和单线程的时候应该是一模一样的。
    @linzyjx step0 做的事情比较杂,就是一些赋值,一些 if 语句什么的,没有什么特点,没有计算密集的任务,也没有 io 密集的任务,都是在内存里倒来倒去。这是一个模拟芯片的程序,step0 在模拟芯片内信号的传递。
    darklinden
        4
    darklinden  
       23 小时 50 分钟前
    如果你 step0 为了多线程使用做了大内存数据的复制,而单线程时只读/无冲突所以用了同一数据,可能 io 耗时超过计算减少的耗时的吧
    JoeJoeJoe
        5
    JoeJoeJoe  
    PRO
       23 小时 49 分钟前
    感觉 op 这个不太适合这么拆分优化.

    如果有前后依赖关系的话, 感觉应该把 step0-3 打包起来放在一个线程里面执行, 再来任务的话就再起一个线程执行 step0-3.
    MrVito
        6
    MrVito  
       23 小时 33 分钟前   1
    你试下绑核吧,说不定有奇效
    AnroZ
        7
    AnroZ  
       23 小时 26 分钟前
    整体看还行吧,原来执行要花 17.5s ,现在 14s 。
    如果你的 step 里涉及到 io 操作的话,测试时间本来就有一定的随机性。
    建议,测 100+次,会更好一些
    RinGress
        8
    RinGress  
       23 小时 15 分钟前
    @MrVito 前几年干过类似的活(但是是图像处理相关的,一张图数据量大,算法有并行优化空间),开 o3 、绑核之类操作加上去确实提升很多(但单线程在这些操作下可能也有一定提升,只是多线程提升可能大一点),再细就要细细看编译器生成的汇编去优化了。
    @AnroZ 这种测量确实需要定量反复多次去做。
    Building
        9
    Building  
       23 小时 10 分钟前 via iPhone
    这个要看多线程任务和访问的数据是不是互相独立的,否则访问时会产生多次拷贝和加锁,相比单线程会更加耗时
    sjkdsfkkfd
        10
    sjkdsfkkfd  
       22 小时 1 分钟前
    需要具体看 step1,2,3 在干啥。通用的来讲,你可以使用自顶向下的微架构分析方法来一层层看问题在什么地方,推荐阅读 《现代 CPU 性能分析与优化》 这本书
    jones2000
        11
    jones2000  
       21 小时 18 分钟前
    设计有问题。 上锁了, 避免用锁。锁很耗时间的。
    minami
        12
    minami  
       21 小时 13 分钟前
    不了解你的各个 step 怎么拆分的,但是如果你需要主线程等待所有消费者线程运行结束的话,建议用 openmp/tbb ,别自己写
    Ainokiseki
        13
    Ainokiseki  
    OP
       21 小时 9 分钟前
    @minami 我等待子线程是 enqueue 之后返回一个 future ,之后调用 join ,总之是用的 c++多线程库

    @JoeJoeJoe 我也想,task 粒度肯定越大越好,但是每个 step 之后都需要同步一下,要不然我担心出问题

    @MrVito 我试了试,绑了核心之后 context switch 不降反增。。搞不懂为什么。我已经按 ai 的建议排除掉了 interrupt 最多的那些核心,但还是不行
    minami
        14
    minami  
       21 小时 4 分钟前
    @Ainokiseki #13 不能这么写,你要用事件等。首先你每个子线程间不能有锁,其次你要给每个子线程注册一个完成的事件,然后在主线程用 WaitForMultipleObjects 之类的机制去等所有事件完成
    ivan_wl
        15
    ivan_wl  
       21 小时 2 分钟前
    后台线程和主线程是不同的 cpu 吗
    chashao
        16
    chashao  
       20 小时 50 分钟前
    @sjkdsfkkfd 感谢分享这本书
    MrVito
        17
    MrVito  
       20 小时 44 分钟前
    @Ainokiseki task 做完以后是 notify_one 还是 notify_all ?有可能惊群了
    unused
        18
    unused  
       20 小时 37 分钟前
    看看是不是 NUMA 的问题,profiling 可以再细一点
    bluearc
        19
    bluearc  
       18 小时 35 分钟前
    @Ainokiseki #3 意思是每次回到 step0 后,线程池中就没有任务了吗?那我觉得这里还可以优化一下(但看描述似乎下一个 step0 必须在上一个 step 执行完成后才能启动?),开一个大的线程池,把 step0 包装一下也扔到线程池里,step 执行完后启动 task2 ,3 的时候传一个回调函数进去,同时设计一个数据结构至少包含一个 atomic<size_t>来同步状态(如果能用 C++20 那可以用 latch ),比如初值为 2 ,task2 ,3 执行完成后就 fetchsub ,再检查值是否为零,如果是 0 就把回调扔到线程池里。
    但 step 单线程和多线程下用时增长,倒也没什么思路,不如换个机器测测,或者像上面说的构造多个不同的数据多测几百次统计平均值
    setname
        20
    setname  
       1 小时 29 分钟前
    1. 换无锁队列,有可能是检查队列的时候造成的顺序堵塞
    2. 检查一下 task 结束的时候有没有释放资源
    3. vector 扩容时间可能比较长,可以做一套流控,使用固定大小队列,可以减少资源消耗
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5587 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 32ms UTC 03:16 PVG 11:16 LAX 19:16 JFK 22:16
    Do have faith in what you're doing.
    ubao msn 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