spinlock在多核处理器上的性能比较

最近在看“多处理器编程的艺术”,其中讲解自旋锁的章节甚是精彩。这是我第一遍就看个 9成明白了的几个重要章节之一。觉得此书大有玩味之必要,于是又复习了一下量化体系结构的牛书。

     突然想到,其中书中提到的TAStest-and-set)/TTAS(test-test-and-set)锁中遇到的问题是不是在现在流行的“多核处理器”也存在呢?即,在解锁时存在着总线“流量风暴”,导致所有其他处理器的cache对应项进行没有意义的”flush”?从我对multicore的理解上看,这点应该是存在的!咱也“量化”一下这个“风暴”的代价,看看有多大!

    我找了台Intel Xeon机器,两个物理处理器,每颗处理器4核,整个系统共8核。看了看内核代码,可以断定普通应用程序看到的cpu{0,2,4,6}是第一个物理处理器上,cpu{1,3,5,7}在另一个物理处理器上。这样,我们就可以使用简单的sched_setaffinity()测试了,代码如下(有点糙),代码简单得很,不再废话解释它们了。



#define _GNU_SOURCE
#include <sched.h>

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/syscall.h>
#define MAX_NR 0xffffff

unsigned long counter = 0;
pthread_spinlock_t lock;

static void setaffinity(int cpu)
{
        cpu_set_t mask;
        pid_t pid = syscall(__NR_gettid);

        CPU_ZERO(&mask);
        CPU_SET(cpu, &mask);
        sched_setaffinity(pid, sizeof(mask), &mask);
}

static void* add_counter(void *pcore)
{
    setaffinity((int)(long)pcore);

    while (counter == 0)
        ;

    while (counter < MAX_NR) {
        pthread_spin_lock(&lock);
        counter++;
        pthread_spin_unlock(&lock);
    }
}

int main(int argc, char *argv[])
{
    pthread_t threads[4];
    unsigned long i;
    pid_t pid;
    struct timespec start, end;
    unsigned long long diff_ns;

    if (pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE)) {
        puts("pthread_spin_init");
        exit(-2);
    }

    for (i=0; i<4; i++) {
        if (argc == 2 && argv[1][0] == 's') {
    //    At same physical socket

        if (pthread_create(threads+i, NULL, add_counter, (void*)(i*2))) {
            puts("pthread_create");
            exit(-1);
        }
        } else {
    //    Across two physcial sockets

        if (pthread_create(threads+i, NULL, add_counter, (void*)(i))) {
            puts("pthread_create");
            exit(-1);
        }
        }
    }
    usleep(5000000);

    clock_gettime(CLOCK_REALTIME, &start);
    counter = 1;
    for (i=0; i<4; i++) {
        if (pthread_join(threads[i], NULL)) {
            puts("pthread_join");
            exit(-1);
        }
    }
    clock_gettime(CLOCK_REALTIME, &end);

        if (start.tv_sec == end.tv_sec)
                diff_ns = end.tv_nsec - start.tv_nsec;
        else {
                diff_ns = 1000000000ULL - start.tv_nsec;
                diff_ns += end.tv_nsec;
                diff_ns += 1000000000ULL * (end.tv_sec - start.tv_sec - 1);
        }
        
        printf("%llums\n", diff_ns/1000000ULL);

    pthread_spin_destroy(&lock);

    return 0;
}



分别运行了 12次,结果如下:

   A. 4个线程在同一个物理处理器上的结果(单位:1/1000s):

    2186+2403+2277+2171+2308+2252+2295+2186+2392+2308+2183+2373 = 27334ms

   B. 4个线程在不同物理处理器上的结果(单位:1/1000s):

    3211+2891+2999+3390+3969+2423+3555+3420+3019+3773+3527+2916 = 39093ms

   看看A情况下到底比B快了多少:

    27334ms / 39093ms = 69.92%

   A居然B快了30%!看来多核编程还真是门艺术。

   且慢且慢,posix_spin_lock()是用TASTTAS方式实现的么?呵呵呵,的确与书上说得不同,我看到的内核实现(futex)是用compare-and-exchange,但我想两者并没有本质的不同。

   综上所述吧,争用激烈的线程最好还是放在同一个核上,否则伤及无辜总是不好的。

你可能感兴趣的:(JOIN,编程,cache,测试,null)