我正在制作一个操纵不同尺寸图像的程序.这些操作中的许多操作从输入读取像素数据并写入单独的输出(例如模糊).这是基于每个像素完成的.
这种图像映射在CPU上非常紧张.我想用多线程来加快速度.我该怎么做?我想要为每行像素创建一个线程.
我有几个要求:
必须最小化可执行文件大小.换句话说,我不能使用大量的库.什么是C/C++最轻量级的便携式线程库?
必须最小化可执行文件大小.我想有一个函数forEachRow(fp*),它为每一行运行一个线程,甚至是一个forEachPixel(fp*),其中fp在自己的线程中的一个像素上运行.哪个最好?
我应该使用普通函数或函子或函数或某些lambda函数还是......其他什么?
某些操作使用优化,这些优化需要处理前一个像素的信息.这使得forEachRow有利.即使考虑到这一点,使用forEachPixel会更好吗?
我需要锁定只读和只写数组吗?
输入仅从中读取,但许多操作需要从阵列中的多个像素输入.
每个像素只输出一次输出.
速度也很重要(当然),但优化可执行文件大小优先.
谢谢.
有关这个主题的更多信息:C++并行化库:OpenMP与线程构建块
不要轻易踏上穿线! 竞争条件可能是屁股的主要痛苦.特别是如果你没有很多线程经验! (你已被警告过:这里有龙!大毛茸茸的非确定性不可能 - 可靠地再现龙!)
你知道什么是死锁吗?Livelock怎么样?
那说......
正如ckarmann和其他人已经建议的那样: 使用工作队列模型.每个CPU核心一个线程. 将工作分解为N块.像许多行一样,使块块相当大.当每个线程变为空闲时,它会阻塞队列中的下一个工作块.
在最简单的IDEAL版本中,你有N个核心,N个线程和问题的N个子部分,每个线程从一开始就知道它将要做什么.
但由于启动/停止线程的开销,这通常不会在实践中发生.你真的希望线程已经产生并等待动作.(例如,通过信号量.)
工作队列模型本身非常强大.它允许您并行化快速排序等内容,这些内容通常不会优雅地在N个线程/核心之间并行化.
比核心更多的线程?你只是浪费头脑.每个线程都有开销.即使在#threads =#cores,你也永远无法实现完美的Nx加速因子.
每行一个线程效率非常低!每个像素一个线程?我甚至不想考虑它.(使用矢量化处理器单元时,每像素方法更有意义,就像它们在旧Crays上一样.但不是线程!)
图书馆?你的平台是什么?在Unix/Linux/g ++下我建议使用pthreads和semaphores.(Pthreads也可以在带有微软兼容层的Windows下使用.但是,uhgg.我真的不相信它!Cygwin可能是更好的选择.)
在Unix/Linux下,man:
* pthread_create, pthread_detach. * pthread_mutexattr_init, pthread_mutexattr_settype, pthread_mutex_init, * pthread_mutexattr_destroy, pthread_mutex_destroy, pthread_mutex_lock, * pthread_mutex_trylock, pthread_mutex_unlock, pthread_mutex_timedlock. * sem_init, sem_destroy, sem_post, sem_wait, sem_trywait, sem_timedwait.
有些人喜欢pthreads的条件变量.但我总是喜欢POSIX 1003.1b信号量.它们处理你希望在开始等待之前发出另一个线程信号的情况.或者多次发出另一个线程的信号.
哦,帮自己一个忙:将你的线程/互斥/信号量pthread调用包装到几个C++类中.这将简化很多事情!
我需要锁定只读和只写数组吗?
这取决于您的精确硬件和软件.通常,只读数组可以在线程之间自由共享.但有些情况并非如此.
写作大致相同.通常,只要只有一个线程写入每个特定的内存点,你就可以了.但有些情况并非如此!
写作比读书更麻烦,因为你可以进入这些奇怪的fencepost情况.内存通常写为单词而不是字节.当一个线程写入单词的一部分,而另一个线程写入不同的部分时,根据哪个线程执行什么时的确切时间(例如,非确定性),您可以得到一些非常不可预测的结果!
我会安全地玩:给每个线程自己的读写区域副本.完成后,将数据复制回来.当然,所有都在互斥下.
除非你在谈论千兆字节的数据,否则内存blits非常快.这几微秒的性能时间不值得调试噩梦.
如果您要使用互斥锁在线程之间共享一个公共数据区域,那么碰撞/等待互斥效率低下就会堆积并破坏您的效率!
看,干净的数据边界是良好的多线程代码的本质.如果你的界限不明确,那就是你遇到麻烦的时候.
同样,保持边界上的所有内容都是必不可少的!并保持互斥区域短!
尽量避免同时锁定多个互斥锁.如果您确实锁定了多个互斥锁,请始终按相同顺序锁定它们!
尽可能使用ERROR-CHECKING或RECURSIVE互斥锁.快速互斥体只是在寻找麻烦,实际(测量)速度增益非常小.
如果遇到死锁情况,请在gdb中运行,点击ctrl-c,访问每个线程并回溯.你可以很快找到问题.(Livelock要困难得多!)
最后一个建议:构建单线程,然后开始优化.在单核系统上,您可能会发现自己从诸如foo [i ++] = bar ==>*(foo ++)= bar之类的东西中获得更多速度,而不是从线程中获得更多速度.
附录: 我所说的关于保持互联网区域短缺的说法?考虑两个线程:(给定Mutex类的全局共享互斥对象.)
/*ThreadA:*/ while(1){ mutex.lock(); printf("a\n"); usleep(100000); mutex.unlock(); } /*ThreadB:*/ while(1){ mutex.lock(); printf("b\n"); usleep(100000); mutex.unlock(); }
会发生什么?
在我的Linux版本下,一个线程将持续运行而另一个线程将挨饿.当mutex.unlock()和mutex.lock()之间发生上下文交换时,它们很少会改变位置.
附录: 在您的情况下,这不太可能是一个问题.但是对于其他问题,可能事先不知道特定工作块需要多长时间才能完成.将问题分解为100个部分(而不是4个部分)并使用工作队列将其拆分为4个核心,从而消除了这些差异.
如果一个工作块比另一个工作块完成时间长5倍,那么它最终都会变得平坦.虽然有太多的块,但获取新工作块的开销会产生明显的延迟.这是一个特定问题的平衡行为.
如果您的编译器支持OpenMP(我知道VC++ 8.0和9.0就像gcc一样),它可以使这样的事情变得更容易.
你不只是想做很多线程 - 当你开始获得越来越多的上下文切换时,添加新线程会减慢回报,这会导致收益递减.在某些时候,使用太多线程实际上可以使并行版本比使用线性算法慢.最佳线程数是可用的cpus /核心数量的函数,以及每个线程在I/O等事物上花费的时间百分比.看一下Herb Sutter的这篇文章,讨论并行性能提升的一些讨论.
OpenMP使您可以轻松地将创建的线程数量调整为可用的CPU数量.使用它(特别是在数据处理案例中)通常只需#pragma omp
在现有代码中添加几个s,并让编译器处理创建线程和同步.
通常 - 只要数据不变,您就不必锁定只读数据.如果您可以确定每个像素槽只会写一次,并且您可以保证在开始读取结果之前已完成所有写入,则您也不必将其锁定.
对于OpenMP,就函子/函数对象而言,不需要做任何特殊的事情.用最合适的方式写出来.以下是英特尔的图像处理示例(将rgb转换为灰度):
#pragma omp parallel for for (i=0; i < numPixels; i++) { pGrayScaleBitmap[i] = (unsigned BYTE) (pRGBBitmap[i].red * 0.299 + pRGBBitmap[i].green * 0.587 + pRGBBitmap[i].blue * 0.114); }
这会自动分成与CPU一样多的线程,并为每个线程分配一个数组部分.
我会推荐boost::thread
和boost::gil
(通用图像库).因为涉及的模板非常多,所以我不确定代码大小是否仍然可以接受.但它是提升的一部分,所以它可能值得一看.