不知有多久没来CSDN写文章了,今天终于勤快了一回,把最近一周的一点业余研究心得总结成这个文章。研究这个问题,是为了寻找在OpenCL之外,可以优化CPU执行效率的方法,优化对三维场景中大量物体实时计算的效率。比如拾取,变换,物理效果之类。之前实践了OpenCL和DirectCompute,但是只能在公司的高级电脑上,在很多只有集成显卡而且还跑着WinXP的电脑上是无缘这些了。而本文要介绍的这个方法实现起来比较简单,只要有多核CPU就能加速,对操作系统也几乎没要求,更不需要搞复杂的 CUDA, OpenCL,DirectCompute 之类,所以我这种又懒又穷的人就自己用了。
设计思想
利用多核CPU同时执行多个线程,使每个线程运行在一个CPU核心上,让线程保持连续多次运行,实现了并行计算。整个功能只用到了 Windows API,实现代码极少,根据这种实现思路,只要在提供线程API的操作系统中都可以很容易的实现。对于N核CPU可以达到N倍的并行加速效果。当然,这个用法只限于使用线程函数,加速效果也不能和显卡相比。
实现方法
功能的实现主要是两部分,线程池和线程并行控制。线程池提高了线程连续运行和重复使用的效率,线程并行控制最终实现了并行加速。
(1).线程池 (Thread Pool)
线程池是用来维持线程生命周期,重复执行多次任务,减少操作系统维护时间的一种线程管理方法。一个线程在创建后,一旦运行结束,这个线程就不存在了,操作系统在创建线程和线程执行结束时都需要做很多底层操作。如果执行大量的异步任务,只是简单的给每次任务逐个创建线程,操作系统会浪费很多时间和资源,最终多个线程并行的时间很难比顺序执行更快。线程池使一个线程重复执行多次线程函数,而这个线程不会立刻结束,这样就保证了线程在运行多个任务时始终存在。
最简单的线程池的实现如下:
//用户函数类型:
typedef void (*UserThreadProc)();
//创建一个执行队列(其实就是数组),把它作为参数传递给下面的内部线程函数,在线程中执行这个队列。
std::vector<UserThreadProc> tasklist;
//内部的线程函数,这个是线程直接运行的函数,在这个函数里调用队列中的函数。
DWORD __stdcall _InternalThreadProc(LPVOID args)
{
std::vector<UserThreadProc>* tasklist = (std::vector<UserThreadProc>*)args;
for(std::vector::iterator i = tasklist->begin(); i!=tasklist->end(); i++)
{
(*i)();
}
tasklist->clear();
return NULL;
};
//应用程序如下:
void MyTaskFunc()//实现一个线程函数作为任务。
{
//Do some hard work.
};
void main()
{
// 向队列中放入100个任务,这里只是简单的重复调用”MyTaskFunc()”。
tasklist.resize(100, MyTaskFunc);
// 使用Windows API创建一个线程,但是指定线程函数为"_InternalThreadProc",把执行队列"tasklist"作为参数传入。
HANDLE thread = CreateThread(NULL, NULL, _InternalThreadProc, &tasklist, NULL, NULL);
// 现在这个线程开始执行这100次任务了。
WaitForSingleObject(thread, INFINITE);//等待完成即可。
};
这个线程池只是个简单的例子,没有用户参数,也不能控制线程的运行,毫无实用意义,需要继续改进。改进后如下:
//定义一个有参数和返回值的用户函数类型
typedef __int64 (*UserThreadProc)(__int64 user_args);
//然后定义一个结构保存异步执行的信息。
struct AsyncState
{
UserThreadProc func; //用户执行的函数。
__int64 params; //用户函数的参数。通过保存地址可以转换成任意类型。
__int64 result; //用户函数的返回值。通过保存地址可以转换成任意类型。
bool is_finished; //指示是否执行完成。由底层来设置,调用者就别随意修改了。
};
//现在可以定义一个线程池对象:
class CThreadPool
{
private:
bool m_enable;
HANDLE m_thread;
std::queue<AsyncState*> m_tasklist;
public:
CThreadPool();
~CThreadPool();
void enqueue(AsyncState* user_call);
size_t curr_pending_tasks();
};
//然后实现一个改进的线程函数,把线程池对象自身当做参数传入。
DWORD __stdcall _InternalThreadProc(LPVOID args)
{
CThreadPool* tp = (CThreadPool*)args;
while(tp->m_enable)
{
while(tp->m_tasklist.size())
{
AsyncState* as = tp->m_tasklist.front();
as->result = as->func(as->params);
as->is_finished = true;
tp->m_tasklist.pop();
}
SuspendThread(tp->m_thread);
}
};
//然后CThreadPool可以用以下的代码实现:
CThreadPool::CThreadPool()
{
this->m_enable = true;
this->m_thread = CreateThread(NULL,NULL,_InternalThreadProc,this,CREATE_SUSPENDED,NULL);
}
CThreadPool::~CThreadPool()
{
this->m_enable=false;
ResumeThread(this->m_thread);
WaitForSingleObject(this->m_thread,INFINITE);
}
void CThreadPool::enqueue(AsyncState* user_call)
{
user_call->is_finished=false;
this->m_tasklist.push(user_ca