最近在看“多处理器编程的艺术”,其中讲解自旋锁的章节甚是精彩。这是我第一遍就看个
9成明白了的几个重要章节之一。觉得此书大有玩味之必要,于是又复习了一下量化体系结构的牛书。
突然想到,其中书中提到的TAS(test-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()是用TAS/TTAS方式实现的么?呵呵呵,的确与书上说得不同,我看到的内核实现(futex)是用compare-and-exchange,但我想两者并没有本质的不同。
综上所述吧,争用激烈的线程最好还是放在同一个核上,否则伤及无辜总是不好的。