V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
tom82232
V2EX  ›  问与答

C 语言多进程 vs 多线程性能问题

  •  
  •   tom82232 · 2020-11-02 15:07:03 +08:00 · 1258 次点击
    这是一个创建于 1519 天前的主题,其中的信息可能已经有所发展或是发生改变。
    测试源码如下:
    #define _GNU_SOURCE
    #include <sched.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <pthread.h>
    #include <stdint.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <sys/types.h>
    #include <fcntl.h>

    typedef struct __test{
    uint64_t a;
    uint64_t b;
    uint64_t c;
    uint64_t d;
    uint64_t e;
    }test_t;


    #define CPU_MAX 128
    int cpuid[CPU_MAX];
    test_t tdata[CPU_MAX];
    int seq[CPU_MAX];
    int thd_num = 1;
    uint64_t prev[CPU_MAX];
    uint64_t tmp[CPU_MAX];
    int flag_stat = 0;

    static void
    init(void)
    {
    for(int i = 0; i < CPU_MAX; i++) {
    cpuid[i] = -1;
    seq[i] = i;
    tdata[i].e = 100;
    tdata[i].a = 0;
    tdata[i].b = 0;
    tdata[i].c = 0;
    tdata[i].d = 0;
    prev[i] = 0;
    }
    }

    static int
    parse_cpuid(const char *str)
    {
    char buf[256];
    int flag_start = 0;
    int thdnum = 0;
    int len = strlen(str);
    if(len > 255) {
    memcpy(buf, str, 255);
    buf[255] = 0;
    }
    else if(0 < len) {
    strcpy(buf, str);
    }
    else return 0;

    for(int i = 0; i < 256; i++) {
    if(buf[i] == ' ' || buf[i] == '\t' || buf[i] == ',') buf[i] = 0;
    }

    for(int i = 0; i < 256; i++) {
    if(flag_start && 0 == buf[i]) flag_start = 0;
    if(0 == flag_start && 0 != buf[i]) {
    flag_start = 1;
    cpuid[thdnum] = atoi(buf+i);
    if(0 > cpuid[thdnum] || CPU_MAX <= cpuid[thdnum]) return -1;
    thdnum++;
    }
    }

    return 0;
    }
    static int
    parse_cmd(int argc, char **argv)
    {
    if(0 < argc) {
    thd_num = atoi(argv[0]);
    if(0 > thd_num) return -1;
    argc--, argv++;
    }

    if(0 < argc) {
    return parse_cpuid(argv[0]);
    }

    return 0;
    }

    static void*
    task_root(void *arg)
    {
    register int seq = *(int*)arg;
    int cid = cpuid[seq];
    cpu_set_t cpumask;
    pid_t pid = syscall(__NR_gettid);

    if(0 <= cid && CPU_MAX > cid) {
    //绑定 CPUID
    CPU_ZERO(&cpumask);
    CPU_SET(cid, &cpumask);
    sched_setaffinity(pid, sizeof(cpumask), &cpumask);
    usleep(1000);
    printf("task[%d] run on cpuid=%d\n", seq, cid);
    }
    else {
    printf("task[%d] run on cpuid=auto\n", seq);
    }

    while(1) {
    tdata[seq].a += 1;
    tdata[seq].b += tdata[seq].e*8;
    tdata[seq].c += 1;
    tdata[seq].d += tdata[seq].e*8;
    }

    return NULL;
    }

    static void*
    stat_root(void *arg)

    {
    arg = arg;
    uint64_t ttl;
    uint64_t d10[CPU_MAX];

    while(1) {

    if(0 == flag_stat) {
    usleep(1000);
    }
    else {
    flag_stat = 0;
    ttl = 0;
    for(int i = 0; i < thd_num; i++) {
    tmp[i] = tdata[i].a;
    }

    for(int i = 0; i < thd_num; i++) {
    tmp[i] = tdata[i].a;
    d10[i] = tmp[i] - prev[i];
    ttl += d10[i];
    }

    printf("\nTotal do times: %lu(sec)\n", ttl/10);
    for(int i = 0; i < thd_num; i++) {
    if(-1 == cpuid[i]) {
    printf("\ttask[%d] cpu=auto do times: %lu(/sec)\n", i, d10[i]/10);
    }
    else {
    printf("\ttask[%d] cpu=%d do times: %lu(/sec)\n", i, cpuid[i], d10[i]/10);
    }

    prev[i] = tmp[i];
    }
    }
    }

    return NULL;
    }

    static void*
    timer_root(void *arg)
    {
    arg = arg;
    uint64_t ttl;
    uint64_t d10[CPU_MAX];

    while(1) {
    sleep(10);
    flag_stat = 1;
    }

    return NULL;
    }

    static int
    task_set(void)
    {
    pthread_t thdid;
    for(int i = 0; i < thd_num; i++) {
    pthread_create(&thdid, NULL, task_root, &seq[i]);
    }

    pthread_create(&thdid, NULL, timer_root, NULL);
    pthread_create(&thdid, NULL, stat_root, NULL);

    return 0;
    }

    int main(int argc, char **argv)
    {
    //program thread-num thread-cpu-seq
    init();
    argc--, argv++;
    if(-1 == parse_cmd(argc, argv)) {
    printf("Input param error\n");
    exit(-1);
    }

    task_set();

    while(1) {
    sleep(1000);
    }

    return 0;
    }

    双 CPU-4 核-超线程 Intel(R) Xeon(R) CPU E5620 @ 2.40GHz
    逻辑 CPU 0 8 2 10 4 12 6 14 1 9 3 11 5 13 7 15
    pysical id 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
    core id 0 0 1 1 9 9 10 10 0 0 1 1 9 9 10 10
    系统:centos8

    测试线程:
    单线程绑定 0 逻辑 CPU 测试结果(执行累加次数) 108363138 (次 /秒)
    双线程绑定 0,1 逻辑 CPU 测试结果(执行累加次数) 79961987 (次 /秒)
    3 线程绑定 0,1,2 逻辑 CPU 测试结果(执行累加次数) 108639280 (次 /秒)
    4 线程绑定 0,1,2,3 逻辑 CPU 测试结果(执行累加次数) 97511278 (次 /秒)
    5 线程绑定 0,1,2,3,4 逻辑 CPU 测试结果(执行累加次数) 115751101 (次 /秒)
    6 线程绑定 0,1,2,3,4,5 逻辑 CPU 测试结果(执行累加次数) 138198505 (次 /秒)
    7 线程绑定 0,1,2,3,4,5,6 逻辑 CPU 测试结果(执行累加次数) 129034696 (次 /秒)
    8 线程绑定 0,1,2,3,4,5,6,7 逻辑 CPU 测试结果(执行累加次数) 126871151 (次 /秒)
    9 线程绑定 0,1,2,3,4,5,6,7,8 逻辑 CPU 测试结果(执行累加次数) 157512706 (次 /秒)
    10 线程绑定 0,1,2,3,4,5,6,7,8,9 逻辑 CPU 测试结果(执行累加次数) 141989197 (次 /秒)
    [结论]
    增加线程数量并没有想象中的增加性能。
    预期的目标应该是增加线程数(在同一个 CPU 中分配给不同的 CORE 的时候性能应该倍数增加,实际上没有),和预期的相差很大。

    测试进程:
    每个进程开始一个累加统计线程
    测试结果
    进程 1 绑定 CPU0 测试结果(执行累加次数) 102847956 (次 /秒)
    进程 2 绑定 CPU2 测试结果(执行累加次数) 102903297 (次 /秒)
    进程 3 绑定 CPU4 测试结果(执行累加次数) 102909125 (次 /秒)
    进程 4 绑定 CPU6 测试结果(执行累加次数) 102934502 (次 /秒)
    进程 5 绑定 CPU3 测试结果(执行累加次数) 107560180 (次 /秒)
    假如开一个进程,绑定 CPU 和别的进程绑定的 CPU 在同一个 CPU 的同一个 CORE 下的话,性能会变成一半,两个进程加起来和单独只开一个进程的时候差不多,
    [结论]
    开进程处理数据没有相互影响,和预期的一样,增加进程性能倍数增加。
    两个进程开在同一个 CPU 的同一个 CORE 中的时候性能减半,因为使用同一套 CORE 的计算单元,所以可以理解。

    [疑问]
    多线程的性能变化应该和多进程的一样预期,实际测试结果多线程的性能提升很少。(到底是什么因素导致的这个测试结果,没有思路,有知道的大佬能告诉一下吗???
    6 条回复    2020-11-02 17:36:42 +08:00
    msg7086
        1
    msg7086  
       2020-11-02 15:16:02 +08:00
    你用多线程跑的时候,CPU 吃满了吗?
    tom82232
        2
    tom82232  
    OP
       2020-11-02 15:18:46 +08:00
    @msg7086 CPU 是 100%us
    msg7086
        3
    msg7086  
       2020-11-02 15:46:33 +08:00
    @tom82232 我的意思是,每个 Core 都吃满了么。然后就是看看多线程下的算力和时间计算有没有错。
    上面代码贴得太乱实在读不下去了。
    besto
        4
    besto  
       2020-11-02 16:10:33 +08:00
    先说点 别的,timer 做的毫无意义,你这里面全部没 mutex,不如 stat 的时候就一个简单逻辑 sleep(x), 然后直接算这个期间到底算了多少。多线程没有显著提升我觉得有很大原因是和 cache 命中有关系。 试试加大计算量:
    diff --git a/b.c b/b.c
    index 5168b0d..a92dc5e 100644
    --- a/b.c
    +++ b/b.c
    @@ -28,6 +28,8 @@ uint64_t prev[CPU_MAX];
    uint64_t tmp[CPU_MAX];
    int flag_stat = 0;

    +uint8_t *cache;
    +
    static void
    init(void)
    {
    @@ -116,6 +118,7 @@ tdata[seq].a += 1;
    tdata[seq].b += tdata[seq].e*8;
    tdata[seq].c += 1;
    tdata[seq].d += tdata[seq].e*8;
    +memset(cache, 0, 64*1024*1024);
    }

    return NULL;
    @@ -195,6 +198,7 @@ return 0;

    int main(int argc, char **argv)
    {
    + cache = malloc(64*1024*1024);
    //program thread-num thread-cpu-seq
    init();
    argc--, argv++;
    tom82232
        5
    tom82232  
    OP
       2020-11-02 16:32:29 +08:00
    @besto timer 可以不去考虑,都没关系。累加的时候处理的数据都没有变化,这样也会发生 cache 不命中?
    besto
        6
    besto  
       2020-11-02 17:36:42 +08:00
    @tom82232 说实话,我不能确定是不是 cache 命中导致,但现象让我很怀疑。另一个可怀疑的点就是你的这个计算运算量的方法。。。所以让每个循环多做点事情也可能是减少误差的真正原因
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   977 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 21:17 · PVG 05:17 · LAX 13:17 · JFK 16:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.