书里说的rdtsc(TSC是一个64位的寄存器记录处理器从启动到现在的时钟周期数)把高32位放入edx,低32位放入rax,在多核处理器好像已经不太准确,
- CPU乱序执行之后,无法保证 rdtsc 指令的执行一定是在业务代码执行的之前和之后
- 处理器的变频
- 无法保证每个CPU核心的 TSC 寄存器是同步的
引入了常量速率TSC的特性解决了变频问题 cat /proc/cpuinfo | grep constant_tsc
查看处理器是否支持,但是不能估计时间
对于乱序问题我们可以加cpu级的memory barriermfence
或者用rdtscp,他具有序列化特性,即它会在指令执行前等待之前的所有指令都执行完毕,并且在指令执行后保证没有后续指令会在它之前执行cat /proc/cpuinfo | grep rdtscp
查看是否支持
#include <stdio.h> #include <stdint.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <assert.h> int main() { uint64_t period = 0, period2 = 0; uint64_t low = 0, hight = 0,low2 = 0, hight2 = 0; int fd = open("./test.c", O_RDWR); assert(-1 != fd); __asm__ volatile("rdtscp":"=a"(low), "=d"(hight)); period = hight << 32 | low; for (size_t i = 0; i < 1000000; i++) { read(fd, NULL, 0); } __asm__ volatile("rdtscp":"=a"(low), "=d"(hight)); period2 = hight << 32 | low; printf("%lu\n", period/1000000); printf("%lu\n", period2/1000000); printf("%lu\n", (period2 - period)/1000000); puts("-----------"); close(fd); }
|
grxer@grxer ~/D/s/O/o/6> while true ;taskset -c 1 sudo ./test;end 35475179 35475567 387 ----------- 35475595 35475995 399 ----------- 35476026 35476421 395 ----------- 35476449 35476848 399 ----------- 35476881 35477275 393 ----------- 35477304 35477695 390 ----------- ^C⏎
|
如果想要一个准确时间可以用clock_gettime,会比gettimeofday准一点,gettimeofday和clock_gettime函数返回的是当前系统时钟的时间值,而不是指令的执行时间,所以没用barrier来防止乱序
#include <stdio.h> #include <stdint.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <time.h> #include <assert.h> int main() {
int fd = open("./test.c", O_RDWR); assert(-1 != fd); struct timespec start_time, end_time;
clock_gettime(CLOCK_MONOTONIC, &start_time); for (size_t i = 0; i < 1000000; i++) { read(fd, NULL, 0); } clock_gettime(CLOCK_MONOTONIC, &end_time); double elapsed_time = (end_time.tv_sec * 1e9 + end_time.tv_nsec - start_time.tv_sec * 1e9 - start_time.tv_nsec)/1000000; printf("ns:%f\n", elapsed_time); printf("us:%f\n", elapsed_time / 1000.0); printf("ms:%f\n", elapsed_time / 1e6); }
|
grxer@grxer ~/D/s/O/o/6 [SIGINT]> gcc -g -o test test.c grxer@grxer ~/D/s/O/o/6> while true ;taskset -c 1 sudo ./test;end ns:127.141074 us:0.127141 ms:0.000127 ns:127.567767 us:0.127568 ms:0.000128 ns:126.079839 us:0.126080 ms:0.000126 ns:127.143936 us:0.127144 ms:0.000127 ns:126.850415 us:0.126850 ms:0.000127 ns:121.985531 us:0.121986 ms:0.000122 ns:119.445469 us:0.119445 ms:0.000119 ns:119.942527 us:0.119943 ms:0.000120 ^C⏎
|
两种方式比较一下,看了下cpu主频cpu MHz : 3193.924
那么一条指令周期1 / 3193.924MHz = 0.312975 ns,0.312975*395=123.625125ns
两种方式相差无几
需要测量上下文切换成本,
需要把程序限制到一个cpu上做切换,这次我们用sched_setaffinity
来实现绑定指定处理器而不是用taskset(PS:简单测试了一下fork子进程是会跑在父进程的cpu亲合力掩码)
#define _GNU_SOURCE #include <sched.h> int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask); pid为0设置当前进程否则pid进程 cpusetsize一般给sizeof(cpu_set_t) mask CPU位掩码 的操作 CPU_ZERO():清除集合的内容,让其不包含任何CPU。
CPU_SET():添加cpu到集合中。
CPU_CLR():从集合中移除cpu
CPU_ISSET() :测试cpu是否在集合中。
CPU_COUNT():返回集合中包含的CPU数量。
|
#define _GNU_SOURCE #include <sched.h> #include <errno.h> #include <stdio.h> #include <fcntl.h> #include <assert.h> #include <time.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> int main() { cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(3, &mask); char buf[2]; pid_t rc; int re = sched_setaffinity(0, sizeof(mask), &mask); if (-1 == re) { perror("sched_setscheduler"); exit(EXIT_FAILURE); } int pipefd1[2], pipefd2[2]; if (pipe(pipefd1) < 0 || pipe(pipefd2) < 0) { perror("pipe"); exit(EXIT_FAILURE); }; rc = fork(); if (rc < 0) exit(-1); else if (rc == 0) { for (size_t i = 0; i < 1000000; i++) { read(pipefd1[0], buf, 1); write(pipefd2[1], "a", 1); } } else { struct timespec start_time, end_time; clock_gettime(CLOCK_MONOTONIC, &start_time); for (size_t i = 0; i < 1000000; i++) { write(pipefd1[1], "0", 1); read(pipefd2[0], buf, 1); } clock_gettime(CLOCK_MONOTONIC, &end_time); double elapsed_time = (end_time.tv_sec * 1e9 + end_time.tv_nsec - start_time.tv_sec * 1e9 - start_time.tv_nsec) / 1000000; printf("ns:%f\n", elapsed_time); printf("us:%f\n", elapsed_time / 1000.0); printf("ms:%f\n", elapsed_time / 1e6);
} }
|
查看你一些cpu affinity父子都绑定到了8(100)就是我们的设置的第三个cpu CPU_SET(3, &mask);
grxer@grxer ~> ps -a PID TTY TIME CMD 2677 pts/12 00:00:04 fish 8612 tty2 00:00:00 gnome-session-b 9251 pts/13 00:00:00 tmux: client 9276 pts/14 00:00:00 fish 9316 pts/15 00:00:00 fish 36681 pts/14 00:00:01 gdb 36697 pts/14 00:00:00 test <defunct> 48284 pts/23 00:00:00 fish 50253 pts/24 00:00:00 fish 54215 pts/38 00:00:02 2 54216 pts/38 00:00:02 2 54309 pts/24 00:00:00 ps grxer@grxer ~> taskset -p 54216 pid 54216's current affinity mask: 8 grxer@grxer ~> taskset -p 54215 pid 54215's current affinity mask: 8
|
交替阻塞,交替上下文切换
grxer@grxer ~/D/s/O/o/6 [SIGINT]> gcc -g -o 2 2.c grxer@grxer ~/D/s/O/o/6> ./2 ns:4238.650005 us:4.238650 ms:0.004239
|
这个时间应该再需要减去两次写入,两次读取时间,懒得搞了