最近在看“多处理器编程的艺术”,其中讲解自旋锁的章节甚是精彩。这是我第一遍就看个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,但我想两者并没有本质的不同。
综上所述吧,争用激烈的线程最好还是放在同一个核上,否则伤及无辜总是不好的。
阅读(2459) | 评论(0) | 转发(0) |