计算机领域高速发展,短短几年,曾只存在于影视作品中的“贾维斯”,都不再遥不可及。
就目前的科技水平而言, CPU 的计算性能已经几乎到达极限。我们已经不再追求单核的计算速度,而是着迷于如何将多个独立的计算单元整合到单独的 CPU 中,也就是我们所说的多核 CPU 。
并行编程在提高计算机系统性能和处理大规模数据方面已经具有很多优势了,但如何在已经使用并行编程的基础上再提高性能呢?鼎道的技术工程师研究出了 ump 项目。
ump (user multi-pthread) 即用户态的多线程,是鼎道技术工程师的自主原创项目之一。
该项目运用了并行化编程的思想,结合 Linux 内核的工作队列机制,通过充分利用多核硬件的性能,降低了程序的执行时间。
ump 参考了 Linux 内核代码的工作队列的机制,在程序中为每个 CPU 开启一个工作线程,每个工作线程有一个工作队列。当有工作(一个执行函数,这里称之为一个工作)需要并行执行时,就将该函数通过接口放到一个工作线程的工作队列上。当工作线程检测到工作队列非空时,就从工作队列上按顺序摘取工作并执行。
ump 有两个非常重要的定义,即:
-
typedef void* (*func_t)(void *data, int nr_data, int id);
由于 C 语言是静态语言,在编译时就确定了函数的形式,并且不具有重构特性,所以我们定义了一个固定的函数类型 func_t,用于 ump 对用户输入的识别;所以我们需要将需要放到线程中的任务封装成 func_t 类型。 -
typedef void (*callback_t)(void *data, int id);
并行化编程中一个重要的问题就是当一个线程中的工作做完时如何通知给主线程。 这里 ump 提供了一个 callback_t 类型的回调函数,可以将 func_t 的返回结果通过 callback_t 返回给用户,从而帮助用户解决线程通讯问题。
ump 的接口函数只有三个:
- int ump_init(void):
ump_init() 函数用于初始化 ump 各项参数,主要是根据系统建立 ump 的线程、 工作队列的初始化、内部使用的信号量等初始化;
- int ump_exit(void):
ump_exit()函数用于在不使用 ump 功能时,释放 ump 的资源,例如:线程退出,内存释放等;
- *int ump_set_func(func_t func, void data, int nr_data, int id, callback_t cb):
ump_set_func()函数用于用户将自己的工作放置到工作队列上,是 ump 中最重要的一个函数。
目前该函数实现了:
- 负责均衡 CPU ,即在放置任务时,会循环使用所有的 CPU ,不会将工作放置在同一个 CPU 上;
- 负责放置工作;
- 负责触发线程的执行,即我们将工作放置到线程上的同时,会自动触发线程的执行;
值得注意的是,ump 中不存在对工作线程的状态检测机制——即当任务中有 sleep() 的挂起函数时,对应的工作线程也会挂起,造成后续的工作不能及时执行
那么,ump 到底有什么使用意义呢?我们可以使用如下的代码进行测试:
#include "ump.h"
#include <sys/time.h>
#include <stdio.h>
#define DELAY 10000000
#define TIMES 16
static long number[TIMES];
long routine0(void)
{
long i;
for(i = 0; i < ((long)TIMES * DELAY); i++){
}
return i;
}
void* func(void *data, int nr_data, int id)
{
int i;
int nr = *((int*)data + 1);
for(i = 0; i < nr; i++){
}
number[id] = i;
return NULL;
}
long routine1(void)
{
long sum = 0;
int arg[2];
arg[1] = DELAY;
ump_init();
for(int i = 0; i < TIMES; i++){
number[i] = -1;
arg[0] = i;
ump_set_func(func, &arg, 1, i, NULL);
}
for(int i = 0; i < TIMES; i++){
while(number[i] == -1);
sum += number[i];
}
return sum;
}
int main(int argc, char** argv)
{
struct timeval tv;
unsigned long time;
gettimeofday (&tv , NULL);
time = tv.tv_sec * 1000 + tv.tv_usec;
if(argc == 1){
printf("routine0 is %lu\n", routine0());
}else{
printf("routine1 is %lu\n", routine1());
}
gettimeofday (&tv , NULL);
time = tv.tv_sec * 1000 + tv.tv_usec - time;
printf("delay is %lu\n", time);
return 0;
}
串行执行时间为 47921 ;而使用 ump 后的执行时间为 9799,因此我们可以得出结论:使用 ump 并行化后,执行时间大大减少,性能提升了389%。
ump 提供了并行化的一种方法/机制——有点类似于用户空间的线程池,但由于串行转并行时会有额外的工作,以及并行化之后的线程间通讯问题,所以并不是所有的场景都适合,这需要对投入和回报进行一定的比较。
同时,线程并不是建立越多越好。 当线程多于 CPU 的数量时,实际只有 CPU 个数个线程可以并行执行,其他的线程处于挂起状态,并不能提升性能,相反还会占用内存资源。
但总体来说,程序的并行化可以提高硬件的利用率,提升性能, 这对于提高计算效率、减少资源浪费、处理大规模数据、支持复杂应用和提高用户体验都具有重要的意义。这对于各种领域的计算机系统和应用程序都是至关重要的。
鼎道智联作为原创操作系统 DingOS 的创造者,我们希望可以为用户提供更安全、绿色、便捷的操作体验。未来,我们也将分享更多来自鼎道“攻城狮”们的原创项目或代码,和大家一起探索新兴科技带来的更多可能性。如果你也对鼎道生态、或鼎道的技术感兴趣,想和我们进行更多的交流和探讨,欢迎扫描下方二维码加入鼎道生态~