增加线程数会使程序变慢

Increasing the number of threads makes the program slower

提问人:Luca Mautino 提问时间:11/16/2023 最后编辑:Peter CordesLuca Mautino 更新时间:11/17/2023 访问量:41

问:

我目前正在尝试通过使用读写锁实现一个具有多线程访问的链表。虽然它似乎工作正常,但我一直在试图看看当我增加线程数时性能改进了什么,事实证明性能实际上变得更糟了:

  • 使用 100.000 次操作和 1 个线程,运行时间为 6.59 秒
  • 使用 100.000 次操作和 2 个线程,运行时间为 6.68 秒
  • 使用 100.000 次操作和 4 个线程,运行时间为 6.69 秒

对于不同数量的操作和线程,模式是相同的。虽然速度放缓并不明显,但与串行版本相比,我肯定希望看到某种改进。我尝试更改链表操作的顺序以避免过多的写入锁争用,但问题仍然存在。 (此外,由于某种原因,MacOS 不允许我运行超过 200.000 次操作的程序)

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>

struct list_node_s {
    int data;
    struct list_node_s * next;
};

int Member(int value, struct list_node_s ** head);
int Insert(int value, struct list_node_s ** head);
int Delete(int value, struct list_node_s ** head);
void * threadOp(void * my_rank);

pthread_rwlock_t lock;
long n;
long thread_count;
struct list_node_s * head_p; 


int main(int argc, char ** argv){
    double i = 0;
    clock_t begin = clock();
    n = strtol(argv[1], NULL, 10);
    thread_count = strtol(argv[2], NULL, 10);
    pthread_t * thread_handles = malloc(sizeof(pthread_t) * thread_count);
    long thread;
    

    printf("Will create %ld threads \n", thread_count);
    pthread_rwlock_init(&lock, NULL);
    
    struct list_node_s head = {.data = 0, .next = NULL};
    head_p = &head;
    
    for (i = 1.0; i < n; i++){
        Insert(i, &head_p);
    }

    for (thread = 0; thread < thread_count; thread++)
        pthread_create(&thread_handles[thread], NULL, threadOp, (void *) thread);

    for (thread = 0; thread < thread_count; thread++)
        pthread_join(thread_handles[thread], NULL);

    pthread_rwlock_destroy(&lock);
    clock_t end = clock();

    double time_spent = (double) (end - begin) / (CLOCKS_PER_SEC);

    printf("Elapsed time %lf \n", time_spent);
}

int Member(int value, struct list_node_s ** head){
    struct list_node_s * curr_p = * head;
    
    // traverse list until end or we got to right value
    while(curr_p != NULL && curr_p -> data < value)
        curr_p = curr_p -> next;

    if (curr_p == NULL || curr_p -> data > value){
        return 0;
    }
    else{
        return 1;
    }
}

int Insert(int value, struct list_node_s ** head){

    struct list_node_s * curr_p = * head;
    struct list_node_s * pred_p = NULL; 
    struct list_node_s * temp_p;
    
    while (curr_p != NULL && curr_p -> data < value){
        pred_p = curr_p;
        curr_p = curr_p -> next;
    }
    if (curr_p == NULL || curr_p -> data > value){
        temp_p = malloc(sizeof(struct list_node_s));
        temp_p -> data = value;
        temp_p -> next = curr_p;
        if (pred_p == NULL)
            *head = temp_p;
        else
            pred_p -> next = temp_p;
        return 1;
    }
    else {
        return 0;
    }
}

int Delete(int value, struct list_node_s ** head){
    struct list_node_s * curr_p = * head;
    struct list_node_s * pred_p = NULL;

    while (curr_p != NULL && curr_p -> data < value){
        pred_p = curr_p;
        curr_p = curr_p -> next;
    }
    // value was found and curr_p is not null
    if (curr_p != NULL && curr_p -> data == value){
        // first element in list
        if (pred_p == NULL){ 
            * head = curr_p -> next;
            free(curr_p);
        } else{
            pred_p -> next = curr_p -> next;
            free(curr_p);
        }
        return 1;
    } 
    // value not in list 
    else{ 
        return 0;
    }
}


void * threadOp(void * my_rank){
    long thread = (long) my_rank;
    long local_n = n / thread_count;
    long a = local_n * thread;
    long b = a + local_n;

    printf("Thread %ld is executing\n", thread);
    // half of threads will execute n members
    for (long i = a; i < b; i++){
        pthread_rwlock_rdlock(&lock);
        Member(i % 15, &head_p);
        pthread_rwlock_unlock(&lock);
        if (i % 10000 == 0){ 
            // other half will execute n/4 Ins and n/4 Del
            pthread_rwlock_wrlock(&lock);
            Insert(i, &head_p);
            pthread_rwlock_unlock(&lock);
        }
        else if (i % 10002 == 0)
        { 
            // other half will execute n/4 Ins and n/4 Del
            pthread_rwlock_wrlock(&lock);
            Delete(i, &head_p);
            pthread_rwlock_unlock(&lock);
        }
    }
    return NULL;
}
C 多线程 性能 链接列表 pthreads

评论

1赞 Jérôme Richard 11/17/2023
你测量时间的方式是错误的。 测量 CPU 时间,而不是挂钟时间。您应该使用 或 .这当然就是为什么您认为该程序较慢,而可能并非如此。clockclock_gettimegettimeofday
0赞 Luca Mautino 11/17/2023
@JérômeRichard我已经用clock_gettime替换了时钟,使用 CLOCK_REALTIME 作为参考时钟,但问题仍然存在
0赞 Jérôme Richard 11/18/2023
请注意,您应该改用,但这不应更改计时结果。CLOCK_MONOTONIC
0赞 Luca Mautino 11/18/2023
@JérômeRichard哦,好的,谢谢你的提示
1赞 Jérôme Richard 11/18/2023
最重要的是,工作非常小,与实际工作相比,启动、安排和等待线程的时间可能很长。因此,它无法扩展。需要做更多的工作才能使并行性有用(或者线程的创建/删除不需要包含在基准测试中,因为线程可以在实际应用程序中重用)。此外,与作品相比,插入/删除的时间不可忽略,因此作者可能成为瓶颈。另请注意,/ 往往不会并行扩展。mallocfree

答: 暂无答案