【问题标题】:Locking in OMP regions锁定 OMP 区域
【发布时间】:2016-12-04 06:41:16
【问题描述】:

我正在尝试在我的应用程序中检测某些功能以查看它们需要多长时间。我正在使用链表在内存中记录所有时间。

在此过程中,我引入了一个跟踪列表末尾的全局变量。当我进入一个新的计时区域时,我会在列表的末尾插入一条新记录。相当简单的东西。

但是,我想跟踪的一些函数是在 OpenMP 区域中调用的。这意味着它们可能会被并行调用多次。这就是我难过的地方。

如果这是使用普通的 Pthreads,我只需将对全局变量的访问封装在互斥锁中,然后就可以结束了。但是,我不确定:此策略是否仍适用于 OpenMP 区域中调用的函数?比如,他们会尊重锁吗?

例如(不会编译,但我想明白了):

Record *head;
Record *tail;

void start_timing(char *name) {
    Record *r = create_record(name);
    tail->next_record = r;
    tail = r;
    return r;
}

int foo(void) {
   Record r = start_timing("foo");
   //Do something...
   stop_timing(r);
}

int main(void) {
   Record r = start_timing("main");
   //Do something...
   #pragma omp parallel for...
   for (int i = 0; i < 42; i++) {
       foo();
   }
   //Do some more...
   stop_timing(r);
}

然后我会更新到:

void start_timing(char *name) {
    Record *r = create_record(name);

    acquire_mutex_on_tail();
    tail->next_record = r;
    tail = r;
    release_mutex_on_tail();

    return r;
}

(抱歉,如果这有一个明显的答案 - 我对 OpenMP 框架和一般的多线程相对缺乏经验。)

【问题讨论】:

  • 如果你的 Openmp 是基于 pthreads 构建的(通常是 Linux 或 osx),它应该可以工作。

标签: c multithreading locking openmp


【解决方案1】:

惯用的互斥锁解决方案是使用 OpenMP 锁:

omp_set_lock(&taillock)
tail->next_record = r;
tail = r;
omp_unset_lock(&taillock)

在某处:

omp_lock_t taillock;
omp_init_lock(&taillock);

...

omp_destroy_lock(&taillock);

简单的 OpenMP 解决方案:

void start_timing(char *name) {
    Record *r = create_record(name);
    #pragma omp critical
    {
        tail->next_record = r;
        tail = r;
    }
    return r;
}

这会创建一个绑定到源代码行的隐式全局锁。有关详细讨论,请参阅this question 的答案。

出于实际目的,使用 Pthread 锁也可以,至少对于 OpenMP 基于 Pthreads 的场景。

一句警告

在性能测量代码中使用锁是危险的。内存分配也是如此,这通常也意味着使用锁。这意味着,start_time 的成本很高,而且随着线程的增加,性能甚至会变得更差。这甚至不考虑缓存失效,因为有一个线程分配一块内存(记录),然后另一个线程修改它(尾指针)。

现在,如果您测量的部分需要几秒钟,这可能没问题,但是当您的部分只有数百个周期时,它会导致巨大的开销和干扰。

要创建可扩展的性能跟踪工具,您必须以较大的块预先分配线程本地内存,并让每个线程仅写入其本地部分。

您也可以选择使用一些现有的测量基础设施,例如Score-P

开销和扰动

首先,区分两者(相关概念)。 开销是你花费的额外时间,而扰动是指对你所测量的东西的影响(即你现在测量的东西与没有测量时所发生的不同)。大量的开销是不可取的,但扰动更糟。

是的,您可以通过在昂贵的测量运行期间暂停计时器来避免一些干扰(开销仍然存在)。但是,在多线程上下文中,这仍然是非常有问题的。

  • 减慢一个线程的进度,可能导致其他线程等待它,例如在隐式障碍期间。您如何归因于该线程和其他可传递线程的等待时间?
  • 内存分配通常是锁定的 - 因此如果您在测量运行时分配内存,您将减慢依赖于内存分配的其他线程。您可以尝试使用内存池来缓解,但我会首先避免使用链表。

【讨论】:

  • 感谢 Score-P 链接;我必须更彻底地调查它!问题:我应该提到我目前在进入内部函数之前“暂停”外部函数的计时,以及从内部函数返回的恢复计时。因此,我的计时信息中不应捕获锁和 malloc。如果我在锁定和内存分配之后才开始计时(例如,我 malloc 记录、锁定并将其插入到位,然后开始计时),我还会遇到性能开销问题吗?
  • 请看我添加的最后一段。
猜你喜欢
  • 2023-03-22
  • 1970-01-01
  • 2012-12-15
  • 1970-01-01
  • 1970-01-01
  • 2012-12-13
  • 2021-03-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多