You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++线程池:为何在Android ARM平板上无法完美扩展

线程池在ARM大小核架构上的性能问题

我实现了一个可将线性可分循环任务拆分到多线程执行的C++线程池,该线程池在X86笔记本电脑上能实现微秒级的线性扩展,但在采用大小核(Big.Little)架构的Android ARM平板上,扩展效果远不如预期。

基准测试数据

-----------------------------------------------------------------------------------------------------------------------
Benchmark                                                                             Time             CPU   Iterations
-----------------------------------------------------------------------------------------------------------------------
BM<dispatch_single>/8/real_time                                                    1.17 ms         1.17 ms          598
BM<dispatch_single>/16/real_time                                                   2.33 ms         2.33 ms          300
BM_threadpool<dispatch_threadpool, ThreadPool, num_threads>/8/real_time           0.756 ms        0.672 ms          702
BM_threadpool<dispatch_threadpool, ThreadPool, num_threads>/16/real_time           1.48 ms         1.30 ms          430

理想的完美扩展应使最后两项的壁钟时间约为0.6ms、1.2ms。

线程池头文件实现

#include <stddef.h>

#include <atomic>
#include <thread>
#include <vector>

class ThreadPool {
    struct ThreadStorage {
        size_t start{0};
        size_t end{0};
        ThreadPool* pool{nullptr};
        std::atomic_flag flag{false};
        void main();
    };

   public:
    using function = void(void* context, size_t idx);
    explicit ThreadPool(size_t num_threads);
    ~ThreadPool();
    void QueueTask(function*, void* context, size_t range);

   private:
    std::atomic<bool> terminate{false};  // Stop looking for jobs
    std::atomic_flag finish_flag{false};
    std::vector<std::jthread> threads;
    std::vector<ThreadStorage> storage;
    std::atomic<size_t> completed_tasks{0};  // wait on new jobs or termination
    function* task{nullptr};
    void* ctx{nullptr};
};

线程池实现代码

#include "threadpool.h"

ThreadPool::ThreadPool(size_t num_threads)
    : threads(num_threads - 1), storage(num_threads), completed_tasks(0) {
    for (uint32_t ii = 0; ii < num_threads; ++ii) {
        storage[ii].pool = this;
        storage[ii].id = ii + 1;
        if (ii > 0) {
            threads[ii - 1] =
                std::jthread(&ThreadPool::ThreadStorage::main, &storage[ii]);
        }
    }
}

void ThreadPool::ThreadStorage::main() {
    while (true) {
        {
            flag.wait(false);
            if (pool->terminate) {
                return;
            }
        }
        for (size_t i = start; i < end; ++i) {
            (*(pool->task))(pool->ctx, i);
        }
        flag.clear();
        if ((pool->completed_tasks.fetch_add(1) + 1) == pool->threads.size()) {
            pool->finish_flag.test_and_set();
            pool->finish_flag.notify_one();
        }
    }
}

void ThreadPool::QueueTask(ThreadPool::function* function, void* context,
                           size_t range) {
    {
        size_t start{0};
        const size_t stepsize{range / storage.size()};
        size_t end{stepsize};
        task = function;
        ctx = context;
        completed_tasks = 0;
        for (ThreadStorage& thread : storage) {
            thread.start = start;
            thread.end = end;
            thread.flag.test_and_set();
            thread.flag.notify_one();
            start = end;
            end += stepsize;
        }
    }
    for (size_t i = storage[0].start; i < storage[0].end; ++i) {
        (*task)(ctx, i);
    }
    {
        finish_flag.wait(false);
        completed_tasks = 0;
        finish_flag.clear();
    }
}

ThreadPool::~ThreadPool() {
    terminate = true;
    for (ThreadStorage& thread : storage) {
        thread.flag.test_and_set();
        thread.flag.notify_one();
    }
}

问题解答

1. 如何对代码进行性能分析,找出耗时的具体环节?

  • 硬件级性能采样:在Android平台可使用perf工具(需root权限),通过perf record -g ./your_benchmark采集调用栈和硬件事件(如缓存命中/缺失、指令周期、任务切换次数),再用perf report分析热点函数和瓶颈。
  • 代码插桩计时:在线程唤醒、任务执行前后、同步操作等关键路径插入高精度计时(如std::chrono::steady_clock或Android系统时钟),统计各阶段耗时占比,重点关注同步原语的开销。
  • 系统级工具:用Android Studio的CPU Profiler可视化线程状态(运行、阻塞、休眠),直观查看线程等待同步信号的阻塞时长、任务执行的时间分布。
  • 缓存分析:通过perf stat查看缓存未命中率,判断是否存在伪共享或内存带宽瓶颈——ARM大小核的缓存架构与X86不同,跨核缓存同步开销更高。

2. 为何该线程池在ARM处理器上无法实现完美扩展?(曾考虑线程亲和性问题,但使用sched_setaffinity设置亲和性后运行时性能大幅下降)

  • 大小核调度特性:ARM Big.Little架构中,小核擅长低功耗轻负载任务,大核负责高负载计算。线程池可能将计算密集型任务调度到小核,导致单线程任务执行时间变长,整体扩展效率降低。手动设置亲和性时,若错误绑定到小核或打破系统动态调度策略,会引发更严重的资源竞争。
  • 同步原语的ARM实现差异:std::atomic_flag的wait/notify在ARM平台的底层实现与X86不同,ARM内存模型更弱,同步操作开销更高。线程唤醒延迟、原子操作的总线冲突,会抵消并行收益。
  • 任务拆分负载不均:当前任务拆分是简单的range / storage.size(),若range无法被线程数整除,最后一个线程会多处理剩余任务,加上大小核性能差异,负载不均问题被放大,导致整体耗时变长。
  • 线程唤醒批量开销:QueueTask中逐个调用notify_one唤醒线程,ARM平台上多次唤醒的累积开销比X86大,调整为批量唤醒策略可减少这部分开销。

3. 为何壁钟时间(real_time)与CPU时间差异如此显著?

  • 线程阻塞与调度延迟:壁钟时间是实际经过的时间,CPU时间是所有线程占用CPU的时间总和。差异大说明大量时间线程处于阻塞状态——比如等待atomic_flag::wait唤醒、系统调度线程切换间隙,或大小核任务迁移开销。
  • 系统负载与调度策略:Android后台进程可能占用CPU,导致线程无法及时获得调度,壁钟时间被拉长,但CPU时间只统计线程实际运行的时间。此外,ARM调度器处理大小核切换时存在额外延迟,这段时间不计入CPU时间,但会影响壁钟时间。
  • 同步等待开销:主线程在finish_flag.wait时处于阻塞状态,这段时间不计入CPU时间,但会增加壁钟时间;工作线程等待任务的休眠时间也不会被统计到CPU时间中,但会累积到壁钟时间里。

内容的提问来源于stack exchange,提问作者fabian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 05:13:11