我正在制作一个操纵不同尺寸图像的程序。这些操作中的许多操作从输入读取像素数据并写入单独的输出(例如模糊)。这是基于每个像素完成的。
这种图像映射对CPU非常有压力。我想用多线程来加快速度。我该怎么做?我想要为每行像素创建一个线程。
我有几个要求:
感谢。
有关此主题的更多信息,请参阅好奇:C++ Parallelization Libraries: OpenMP vs. Thread Building Blocks
答案 0 :(得分:13)
答案 1 :(得分:10)
如果您的编译器支持OpenMP(我知道VC++ 8.0 and 9.0和gcc一样),它可以使这样的事情变得更容易。
你不只是想制作很多线程 - 当你开始获得越来越多的上下文切换时,添加新线程会减慢回报,这会导致收益递减。在某些时候,使用太多线程实际上可以使并行版本比使用线性算法慢。最佳线程数是可用的cpu / core数量的函数,以及每个线程在I / O之类的事件上被阻塞的时间百分比。查看Herb Sutter的this article,了解并行性能提升的一些讨论。
OpenMP使您可以轻松地将创建的线程数量调整为可用的CPU数量。使用它(特别是在数据处理案例中)通常只需在现有代码中添加一些#pragma omp
,并让编译器处理创建线程和同步。
通常 - 只要数据不变,您就不必锁定只读数据。如果您可以确定每个像素槽只会写一次,并且您可以保证在开始读取结果之前已完成所有写入,则您也不必将其锁定。
对于OpenMP,就函子/函数对象而言,没有必要做任何特别的事情。用最合适的方式写出来。这是Intel的图像处理示例(将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相同数量的线程,并为每个线程分配一个数组部分。
答案 2 :(得分:6)
我建议boost::thread
和boost::gil
(通用图片库)。因为涉及的模板非常多,所以我不确定代码大小是否仍然可以接受。但这是提升的一部分,所以值得一看。
答案 3 :(得分:2)
答案 4 :(得分:1)
我认为你不希望每行有一个帖子。可能会有很多行,并且您将花费大量内存/ CPU资源来启动/销毁线程以及CPU从一个切换到另一个。此外,如果你有P处理器的C核心,你可能不会有超过C * P线程的大量收益。
我建议你使用定义数量的客户端线程,例如N个线程,并使用应用程序的主线程将行分配给每个线程,或者他们可以简单地从“作业队列”获取指令。当一个线程完成一行时,它可以在此队列中检查要执行的另一行。
至于库,你可以使用boost :: thread,它非常便携,而且不是太重量级。
答案 5 :(得分:1)
我可以问你在写这个平台吗?我猜这是因为可执行文件大小是一个问题,你不是在台式机上。在哪种情况下平台有多个内核或超线程?如果没有,那么向您的应用程序添加线程可能会产生相反的效果并减慢它...
答案 6 :(得分:1)
要优化简单的图像变换,使用SIMD矢量数学要比尝试多线程化程序要好得多。
答案 7 :(得分:1)
查看MSDN上的Creating an Image-Processing Network演练,其中介绍了如何使用Parallel Patterns Library构建并发图像处理管道。
我还建议Boost.GIL,它可以生成高效的代码。有关简单的多线程示例,请查看Victor Bogado的gil_threaded。 An image processing network using Dataflow.Signals and Boost.GIL也解释了一个感兴趣的数据流模型。
答案 8 :(得分:0)
每个像素行一个线程是疯狂的,最好有大约n-1到2n个线程(对于n个cpu),并使每个循环获取一个jobunit(可能是一行或其他类型的分区)
在unix-like上,使用pthreads它简单轻巧。
答案 9 :(得分:0)
也许编写自己的小库,使用#ifdef
为每个平台实现一些标准的线程函数?实际上并没有太大的东西,这会比你可以使用的任何库都减少可执行文件的大小。
更新:对于工作分配 - 将您的图像拆分成碎片并为每个线程分配一块。因此,当它完成这件作品时,就完成了。这样就可以避免实现可以进一步增加可执行文件大小的作业队列。
答案 10 :(得分:0)
我认为无论您选择哪种线程模型(boost,pthread,本机线程等)。我认为你应该考虑一个线程池而不是每行一个线程。线程池中的线程“开始”是非常便宜的,因为就操作系统而言它们已经被创建,这只是给它一些事情要做。
基本上,您可以在池中说4个线程。然后以串行方式,对于每个像素,告诉线程池中的下一个线程来处理像素。这样,您一次有效处理的像素不超过4个像素。您可以根据用户首选项或系统报告的CPU数量来确定池的大小。
这是IMHO向SIMD任务添加线程的最简单方法。
答案 11 :(得分:0)
很可能,瓶颈不是CPU而是内存带宽,因此多线程不会有太大帮助。尝试最小化内存访问并在有限的内存块上工作,以便可以缓存更多数据。我不久前遇到过类似的问题,我决定优化我的代码以使用SSE指令。每个单线程的速度提升几乎是4倍!
答案 12 :(得分:0)
您的编译器不支持OpenMP。另一种选择是使用库方法,英特尔的线程构建模块和Microsoft并发运行时都可用(VS 2010)。
还有一组称为并行模式库的接口,这两个接口都受到两个库的支持,并且这些接口具有模板化的parallel_for库调用。 所以代替:
#pragma omp parallel for
for (i=0; i < numPixels; i++)
{ ...}
你会写:
parallel_for(0,numPixels,1,ToGrayScale());
其中ToGrayScale是函数的函子或指针。 (注意,如果你的编译器支持lambda表达式,你很可能不会将该函数作为lambda表达式内联)。
parallel_for(0,numPixels,1,[&](int i)
{
pGrayScaleBitmap[i] = (unsigned BYTE)
(pRGBBitmap[i].red * 0.299 +
pRGBBitmap[i].green * 0.587 +
pRGBBitmap[i].blue * 0.114);
});
-Rick
答案 13 :(得分:0)
我认为map / reduce框架将是在这种情况下使用的理想选择。您可以使用Hadoop流来使用现有的C ++应用程序。
只需实施地图并减少工作。
正如您所说,您可以使用行级操作作为地图任务,并将行级操作与reduce任务中的最终图像组合。
希望这很有用。
答案 14 :(得分:0)
您还可以使用IPP或Cassandra Vision C ++ API等库,这些库大多比您拥有的代码更优化。
答案 15 :(得分:-3)
还有另一种使用程序集进行优化的选项。现在,一个激动人心的动态代码生成项目是softwire(可追溯到一段时间 - here是原始项目的站点)。它由Nick Capens开发,现已成为商用swiftshader。但是,原始软线的衍生产品仍然可以在gna.org上找到。
This可以作为他的解决方案的介绍。
就个人而言,我不相信你可以通过多线程解决你的问题来获得显着的性能。