Apr
25
1. 虽然都用 -lpthread 来链接到 libpthread.so ,但是实际上 pthread 是POSIX规范规定的接口,不是某一个库的名字。不同的类Unix系统在底层对pthread的实现不一样。
2. 在Linux的实现里,每个pthread线程都对应一个LWP(light weight process);而LWP是由内核线程支持的,由内核统一调度,所以每个pthread线程都有对应task_struct,以及对应的tid(不是pthread_self()返回的那货,而是类似于pid),所以效率就不会太高(因为切换什么的都要回到内核里,而syscall的效率……),而且对应地,还要消耗内核线程的栈空间等资源,所以效率不会很高。
3. man gettid可以看到这个syscall是在 sys/types.h 里,但是实际上在我的ubuntu 12.04, kernel 3.2.0上面没有,需要这样:
不加上的话,结果就是 error: ‘gettid’ was not declared in this scope
4. 每个线程都有单独的cpu_affinity,通过 pthread_setaffinity_np, pthread_getaffinity_np 来读写。
5. Linux下,在 /proc/[PID]/task 下面包含了进程的所有线程的相关信息,每个线程一个目录,目录名就是线程的ID;每个线程的相关信息与进程类似,:
5. 每个线程可以被调度到不同CPU上面,类似于进程的/proc/PID/stat文件,线程当前运行的CPU ID保存在对应目录内的stat文件中第39个字段:
6. 可以用ps的 L 和 F 参数列出所有的LWP:
felix021@xxx:~/code$ ps -FL
UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY TIME CMD
fengmin 7792 7791 7792 0 1 16561 1676 0 16:02 pts/3 00:00:00 -bash
fengmin 8660 7792 8660 0 4 11111 1004 1 17:04 pts/3 00:00:00 ./ttt
fengmin 8660 7792 8662 99 4 11111 1004 1 17:04 pts/3 00:05:05 ./ttt
fengmin 8660 7792 8663 99 4 11111 1004 15 17:04 pts/3 00:05:05 ./ttt
fengmin 8660 7792 8664 99 4 11111 1004 14 17:04 pts/3 00:05:05 ./ttt
fengmin 8692 7792 8692 0 1 16411 996 2 17:09 pts/3 00:00:00 ps -FL
其中LWP列就是LWP的id,PSR是processor,也就是CPU ID。
p.s. 另外有个GNU PTH,这个是 n:1 的,纯用户空间完成调度的线程库,POSIX兼容。
2. 在Linux的实现里,每个pthread线程都对应一个LWP(light weight process);而LWP是由内核线程支持的,由内核统一调度,所以每个pthread线程都有对应task_struct,以及对应的tid(不是pthread_self()返回的那货,而是类似于pid),所以效率就不会太高(因为切换什么的都要回到内核里,而syscall的效率……),而且对应地,还要消耗内核线程的栈空间等资源,所以效率不会很高。
3. man gettid可以看到这个syscall是在 sys/types.h 里,但是实际上在我的ubuntu 12.04, kernel 3.2.0上面没有,需要这样:
#include <sys/syscall.h>
#define gettid() syscall(__NR_gettid)
#define gettid() syscall(__NR_gettid)
不加上的话,结果就是 error: ‘gettid’ was not declared in this scope
4. 每个线程都有单独的cpu_affinity,通过 pthread_setaffinity_np, pthread_getaffinity_np 来读写。
5. Linux下,在 /proc/[PID]/task 下面包含了进程的所有线程的相关信息,每个线程一个目录,目录名就是线程的ID;每个线程的相关信息与进程类似,:
引用
felix021@xxx:/proc/8660$ ls task/
8660 8662 8663 8664
felix021@xxx:/proc/8660:/proc/8660$ ls task/8662
attr cmdline cwd exe fdinfo limits maps mounts oom_score schedstat stat status
auxv cpuset environ fd io loginuid mem oom_adj root smaps statm wchan
8660 8662 8663 8664
felix021@xxx:/proc/8660:/proc/8660$ ls task/8662
attr cmdline cwd exe fdinfo limits maps mounts oom_score schedstat stat status
auxv cpuset environ fd io loginuid mem oom_adj root smaps statm wchan
5. 每个线程可以被调度到不同CPU上面,类似于进程的/proc/PID/stat文件,线程当前运行的CPU ID保存在对应目录内的stat文件中第39个字段:
引用
felix021@xxx/proc/8660$ for i in /proc/8660/task/*; do LWP=`basename $i`; PSR=`awk '{print $39}' $i/stat`; echo $LWP, $PSR; done
8660, 1
8662, 1
8663, 15
8664, 14
8660, 1
8662, 1
8663, 15
8664, 14
6. 可以用ps的 L 和 F 参数列出所有的LWP:
引用
felix021@xxx:~/code$ ps -FL
UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY TIME CMD
fengmin 7792 7791 7792 0 1 16561 1676 0 16:02 pts/3 00:00:00 -bash
fengmin 8660 7792 8660 0 4 11111 1004 1 17:04 pts/3 00:00:00 ./ttt
fengmin 8660 7792 8662 99 4 11111 1004 1 17:04 pts/3 00:05:05 ./ttt
fengmin 8660 7792 8663 99 4 11111 1004 15 17:04 pts/3 00:05:05 ./ttt
fengmin 8660 7792 8664 99 4 11111 1004 14 17:04 pts/3 00:05:05 ./ttt
fengmin 8692 7792 8692 0 1 16411 996 2 17:09 pts/3 00:00:00 ps -FL
其中LWP列就是LWP的id,PSR是processor,也就是CPU ID。
p.s. 另外有个GNU PTH,这个是 n:1 的,纯用户空间完成调度的线程库,POSIX兼容。
Apr
16
今天看到80386+里 BSF/BSR 这对指令貌似挺有意思,Bit Scan Forward/Reverse,它们的作用是正向/逆向扫描一个WORD(16bit)/DWORD(32bit),找出第一个等于1的bit编号。比如对于BX=0x1010,执行 BSF CX, BX 得到的CX=4,而 BSR CX, BX 得到的CX=12。
咋一看是个神器啊。《编程之美》什么的,不是还有一道面试题说的就是怎样快速求出一个整数中bit=1的数量么。这货加上个while循环和移位,秒杀。最新版的Intel指令手册并没有明确说它需要多少个时钟周期,不过这里的数据指出,在80486+,BSF需要6~43个时钟周期才能完成(BSR则需要6~104个clocks),也就是说,它似乎并没有被特别优化,而是使用了CPU内的微码来完成循环,所以不要指望能带来性能上的BONUS,详情可参见这篇:求整数中比特为1的二进制位数。
这篇文章里面给出的用上BSF/BSR的代码是:
想试试这段代码,不过蛋疼的是这是VS的汇编语法,GCC不能识别,于是终于下了决心翻出GCC-Inline-Assembly-HOWTO,看了下,其实也不是很复杂,简单改了下,变成这样:
跑了一下,果然效率不行,跟最差的直接循环在同一个量级。
仔细看了下代码,发现这里用的是BSR,而且还用上了n和count两个变量,感觉不太爽,于是完全改成汇编,写成这样:
结果。。。基本没变。嗯,所以就这样吧。。。
咋一看是个神器啊。《编程之美》什么的,不是还有一道面试题说的就是怎样快速求出一个整数中bit=1的数量么。这货加上个while循环和移位,秒杀。最新版的Intel指令手册并没有明确说它需要多少个时钟周期,不过这里的数据指出,在80486+,BSF需要6~43个时钟周期才能完成(BSR则需要6~104个clocks),也就是说,它似乎并没有被特别优化,而是使用了CPU内的微码来完成循环,所以不要指望能带来性能上的BONUS,详情可参见这篇:求整数中比特为1的二进制位数。
这篇文章里面给出的用上BSF/BSR的代码是:
int f4(unsigned int num) {
int count = 0;
while(num) {
int n;
__asm {
bsr eax, num
mov n, eax
}
++count;
num ^= (1 << n);
}
return count;
}
int count = 0;
while(num) {
int n;
__asm {
bsr eax, num
mov n, eax
}
++count;
num ^= (1 << n);
}
return count;
}
想试试这段代码,不过蛋疼的是这是VS的汇编语法,GCC不能识别,于是终于下了决心翻出GCC-Inline-Assembly-HOWTO,看了下,其实也不是很复杂,简单改了下,变成这样:
int f4(unsigned int num) {
int count = 0;
while(num) {
int n;
__asm__ (
"bsr %1, %%eax\n\t"
"movl %%eax, %0\n\t"
: "=r" (n)
: "r" (num)
: "%eax"
);
++count;
num ^= (1 << n);
}
return count;
}
int count = 0;
while(num) {
int n;
__asm__ (
"bsr %1, %%eax\n\t"
"movl %%eax, %0\n\t"
: "=r" (n)
: "r" (num)
: "%eax"
);
++count;
num ^= (1 << n);
}
return count;
}
跑了一下,果然效率不行,跟最差的直接循环在同一个量级。
仔细看了下代码,发现这里用的是BSR,而且还用上了n和count两个变量,感觉不太爽,于是完全改成汇编,写成这样:
int f4(unsigned int num) { //注:x86下一般都是用EAX来存函数的返回值
asm (
"movl $0, %%eax\n\t" //这里是eax存的就是count
"jmp f4_cmp\n"
"f4_loop:\n\t"
"bsf %0, %%ecx\n\t" //ecx=bsf(num)
"incb %%cl\n\t" //cl += 1
"shrl %%cl, %0\n\t" //num >>= cl; shr貌似只接受 cl 和立即数?
"incl %%eax\n" //count++
"f4_cmp:\n\t"
"cmp $0, %0\n\t"
"jnz f4_loop\n\t"
:
: "r"(num)
: "%eax", "%ecx"
);
}
asm (
"movl $0, %%eax\n\t" //这里是eax存的就是count
"jmp f4_cmp\n"
"f4_loop:\n\t"
"bsf %0, %%ecx\n\t" //ecx=bsf(num)
"incb %%cl\n\t" //cl += 1
"shrl %%cl, %0\n\t" //num >>= cl; shr貌似只接受 cl 和立即数?
"incl %%eax\n" //count++
"f4_cmp:\n\t"
"cmp $0, %0\n\t"
"jnz f4_loop\n\t"
:
: "r"(num)
: "%eax", "%ecx"
);
}
结果。。。基本没变。嗯,所以就这样吧。。。