一、多核心运行程序
在linux下我们可以指定线程或者进程运行在指定的cpu核心上,操作方法如下:
1)运行进程指定cpu核心
taskset -c 2 ./app //-c指定运行的cpu核心号,从0计数,查看效果如下:
2)运行线程指定cpu核心
主要是设置线程的属性,具体代码如下,详看main函数。
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <pthread.h>
#include <time.h>void* thread_func0(void* arg) {int iterations = 1000000000; // 迭代次数double a = 3.14159; // 乘法操作的乘数double b = 2.71828; // 乘法操作的被乘数double sum = 0.0; // 加法操作的累加器struct timespec start, end;clock_gettime(CLOCK_MONOTONIC, &start);// 执行乘法运算for (int i = 0; i < iterations; ++i) {double c = a * b;sum += c; // 将乘法结果加到累加器上}clock_gettime(CLOCK_MONOTONIC, &end);double time_spent = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;printf("cpu0 Performed %d in %f seconds\n", iterations, time_spent);printf("cpu0 Resulting sum: %f\n", sum);return NULL;
}void* thread_func1(void* arg) {int iterations = 1000000000; // 迭代次数double a = 3.14159; // 乘法操作的乘数double b = 2.71828; // 乘法操作的被乘数double sum = 0.0; // 加法操作的累加器struct timespec start, end;clock_gettime(CLOCK_MONOTONIC, &start);// 执行乘法运算for (int i = 0; i < iterations; ++i) {double c = a * b;sum += c; // 将乘法结果加到累加器上}clock_gettime(CLOCK_MONOTONIC, &end);double time_spent = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;printf("cpu1 Performed %d in %f seconds\n", iterations, time_spent);printf("cpu1 Resulting sum: %f\n", sum);return NULL;
}int main() {pthread_t thread0, thread1; // 为每个线程使用不同的变量pthread_attr_t attr0, attr1; // 为每个线程属性使用不同的变量unsigned long mask0 = 1UL << 2; // 将线程绑定到CPU核心2unsigned long mask1 = 1UL << 3; // 将线程绑定到CPU核心3// 初始化线程属性pthread_attr_init(&attr0);pthread_attr_init(&attr1);pthread_attr_setaffinity_np(&attr0, sizeof(mask0), &mask0);pthread_create(&thread0, &attr0, thread_func0, NULL);pthread_attr_setaffinity_np(&attr1, sizeof(mask1), &mask1);pthread_create(&thread1, &attr1, thread_func1, NULL);// 等待线程0和线程1完成pthread_join(thread0, NULL);pthread_join(thread1, NULL);return 0;
}
编译后运行效果如下(代码指定了2和3,从0计数):
3)两者的优先级
代码中的线程属性设置cpu核心为2和3,进程以cpu核心0来运行,实际运行在核心2和3上。
测试代码用上述不变,运行命令为:taskset -c 0 ./app
因此,可得结论:在进程和线程都指定运行的cpu核心时,以线程为准。
二、多核心运行的时间测量需要注意的事项
1)多核计时异常
在测试运行时间时,遇到一个问题,最开始我使用的时间测试代码如下:
clock_t start = clock();//测量开始时间
//功能代码
//...
clock_t end = clock();// 测量结束时间
double time_spent = (double)(end - start) / CLOCKS_PER_SEC;
这个代码在测试单核运行的代码时,时间是对的,但是使用多核运行时,发现这个代码统计的时间是我实际手机计时的2倍,怀疑是该函数统计了本程序对所有cpu的占用时间,即双核的时间。
后修改时间测量代码如下:
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
//功能代码
//...
clock_gettime(CLOCK_MONOTONIC, &end);
double time_spent = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
测试时间和手机测试的一致。
2)clock()
和 clock_gettime()
比较
clock()
和 clock_gettime()
都是 C 语言标准库中用于获取时间的函数,但它们在用途和行为上有一些重要的区别:
-
定义和来源:
clock()
函数定义在<time.h>
头文件中,它测量的是程序占用 CPU 的时间(也称为 CPU 时间或处理器时间)。它返回程序启动以来的时钟周期数,这个值是通过处理器的时钟周期来计算的。clock_gettime()
函数是 POSIX.1-2001 标准的一部分,也定义在<time.h>
中,它提供了更精确的时间测量。它可以测量多种类型的时间,包括实时时间(系统时间)、进程时间、线程时间等。
-
时间类型:
clock()
只能测量程序占用 CPU 的时间。clock_gettime()
可以测量多种时间,通过指定不同的时钟 ID 来获取不同类型的时间。例如:CLOCK_REALTIME
:返回以系统实时时间为基础的时间,受系统时间的更改影响。CLOCK_MONOTONIC
:返回一个单调时钟,它以系统启动为基础,不受系统时间更改的影响,适合测量时间间隔。CLOCK_PROCESS_CPUTIME_ID
:返回调用进程占用的 CPU 时间总和。CLOCK_THREAD_CPUTIME_ID
:返回调用线程占用的 CPU 时间。
-
精度:
clock()
的精度受限于系统和硬件,通常以秒为单位,精度较低。clock_gettime()
通常提供更高的精度,可以测量到纳秒级别。
-
返回值:
clock()
返回一个clock_t
类型的值,表示程序占用 CPU 的时钟周期数。如果失败,返回((clock_t) -1)
。clock_gettime()
成功时返回 0,失败时返回 -1,并设置errno
以指示错误。
-
使用场景:
clock()
适用于需要测量程序执行时间的场景,但它不适用于测量墙上时钟时间(实际时间)。clock_gettime()
适用于需要高精度时间测量的场景,包括但不限于测量墙上时钟时间、系统时间、进程或线程的 CPU 时间。
-
多线程环境:
- 在多线程环境中,
clock()
可能无法准确反映单个线程的 CPU 时间,因为它测量的是整个进程的 CPU 时间。 clock_gettime()
通过CLOCK_THREAD_CPUTIME_ID
可以准确测量单个线程的 CPU 时间。
- 在多线程环境中,
-
系统依赖性:
clock()
是 C 语言标准的一部分,几乎所有的 C 语言环境都支持。clock_gettime()
是 POSIX 标准的一部分,可能在非 POSIX 兼容的系统上不可用或需要特定的编译器标志。
总结来说,clock_gettime()
提供了更多的功能和更高的精度,是现代 C 语言编程中推荐的时间测量函数。而 clock()
由于其较低的精度和对多线程支持的限制,在现代编程中使用较少。