OpenMP 用于图像处理






4.29/5 (6投票s)
OpenMP 用于图像处理
引言
我尝试找一个关于 OpenMP (C/C++ 和 Fortran 在所有架构上进行跨平台共享内存并行编程,http://openmp.org/wp/) 图像处理的简单示例,但没有成功。所以我决定自己深入研究细节。这篇文章很短,因为外面还有很多东西可以阅读。我没有在这里放置完整的项目,因为它只是一个使用的控制台示例。
但是,这里有一些我想与你分享的结果。
使用代码
首先,我们必须找出存在多少物理核心(使用所有核心会降低性能)。这可以通过调用 GetLogicalProcessorInformation
并计算处理器核心数来完成。处理器数量将通过调用 omp_set_num_threads()
设置给 OpenMP 库。
int OMP_Prepare() { SYSTEM_INFO sysinfo; GetSystemInfo( &sysinfo ); DWORD numCPU = sysinfo.dwNumberOfProcessors; LPFN_GLPI glpi; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL, ptr = NULL; glpi = (LPFN_GLPI) GetProcAddress( GetModuleHandle(TEXT("kernel32")), "GetLogicalProcessorInformation"); DWORD processorCoreCount = 0; if (glpi != NULL) { DWORD returnLength = 0; glpi(NULL, &returnLength); buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(returnLength); glpi(buffer, &returnLength); ptr = buffer; DWORD byteOffset = 0; while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) { switch (ptr->Relationship) { case RelationProcessorCore: processorCoreCount++; break; default: break; } byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ptr++; } delete(buffer); } if(processorCoreCount == 0) { processorCoreCount = numCPU / 2; // this is just a guess... if(processorCoreCount == 0) processorCoreCount = 1; } omp_set_num_threads(processorCoreCount); return processorCoreCount; }
大多数代码示例都使用循环内的数组,这比使用指针更耗时。通常,OpenMP 使用类似 'parallel for' 的结构。在此代码行之后,无法初始化指针,因为编译器要求一个 for(...)
循环。
#pragma omp parallel for for(run = 0; run < size; run++) pwdata[index++] <<= 4; // Main loop for processing
我发现,当分离 'parallel
' 和 'for
' 编译指示时,可以在进入循环之前初始化一个指针:
使用指针时,必须设置一个 OMP 线程来处理图像的一部分。
#pragma omp parallel shared (shared) private(private)
{ // Braces are mandatory to set the scope
... do the thread stuff here
}
在花括号内,你可以像 OpenMP 示例中建议的那样设置循环。
在左大括号之后,你可以使用线程 ID 和图像数组的大小将指针设置为各个线程的位置。因此,每个线程都会获得自己的工作区域。假设我们有一个 Core i7 CPU,它有四个双核 CPU。 OMP_Prepare 函数返回 'four',这是可用物理处理器的数量。由于这种初始化,我们将获得四个线程。然后将由四个线程覆盖要处理的区域。现在我们必须将数组分成四个块。块大小和图像大小对所有线程都是通用的,标记为“shared”。数据指针和运行索引是每个线程私有的。在第一个编译指示之后,每个线程都会获得它自己的单独数据指针,这基于 size_per_thread 和线程 ID,线程 ID 是一个当前数字(在我们的例子中,有四个物理 CPU 的情况下为 0,1,2,3)。
完整的函数是这样的:
//OMP void OMP_Work(WORD* datain, long xsize, long ysize, int cpucnt) { unsigned short *pwdata; long run = 0; long size = xsize * ysize; long size_per_thread = size / cpucnt; int id; #pragma omp parallel shared (size_per_thread, size) private(id, pwdata, run) { // Braces are mandatory to set the scope id = omp_get_thread_num(); // Get current thread number pwdata = datain + size_per_thread * id;// Set data pointer to start position for thread #pragma omp for for(run = 0; run < size; run++) *pwdata++ <<= 4; // Main loop for processing } #pragma omp barrier // Wait for all threads to complete }
这是用于获取时间消耗的函数:
为了测量时间,我使用了性能计数器。该函数必须被调用两次:首先进行初始化,然后第二次查询时间。最初我们从 dTTF = 0 开始,它会调用内部初始化。初始化部分查询频率和当前计数器。在此之后,我们准备好进行下一次调用,该调用将获取过去的时间。
double TT() { __int64 T1, T2; double dT; static double dTTF = 0.0; static LARGE_INTEGER LITime1, LITime2; if(dTTF == 0.0) { LARGE_INTEGER LIFrequ; QueryPerformanceFrequency(&LIFrequ); QueryPerformanceCounter(&LITime1); dTTF = (double)LIFrequ.QuadPart; return 0.0; } QueryPerformanceCounter(&LITime2); T1 = LITime1.QuadPart; T2 = LITime2.QuadPart; dT = (double)T2 - (double)T1; dT /= dTTF; LITime1.QuadPart = LITime2.QuadPart; return dT; }
这是用法
首先我们调用准备函数,该函数将获取物理处理器的数量。这个处理器的数量被设置为 OMP 库。现在我们创建一个虚拟数组,该数组将被填充虚拟数据。然后我们调用真正的计时函数 (TT) 以初始化内部时间变量。
调用 Work 来处理数据。目前,'work' 部分只是一个移位。在 'Work' 完成后,我们再次调用 TT() 以获取处理所消耗的时间。
int main(int argc, char* argv[]) { int iCPU = OMP_Prepare(); ix = 2560;//640; iy = 2160;//480; ps = new WORD[ix * iy]; double dt; for(int jjj = 0; jjj < ix * iy; jjj++)//dummy data ps[jjj] = 20; TT(); Work(ps, ix, iy, iCPU); dt = TT(); delete[] ps; }
在 Core i7 上,循环的运行速度大约比没有 OMP 代码的循环快两倍。当进行更多计算而不仅仅是移动一个 WORD
时,性能提升可能会更高。