65.9K
CodeProject 正在变化。 阅读更多。
Home

OpenMP 用于图像处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.29/5 (6投票s)

2012年12月10日

CPOL

3分钟阅读

viewsIcon

26655

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 时,性能提升可能会更高。

© . All rights reserved.