锁定 OMP 区域

ton*_*sdg 2 c multithreading locking openmp

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

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

然而,我想要跟踪的一些函数是在 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);
}
Run Code Online (Sandbox Code Playgroud)

然后我将更新为:

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;
}
Run Code Online (Sandbox Code Playgroud)

(如果有一个明显的答案,我深表歉意 - 我对 OpenMP 框架和多线程总体来说相对缺乏经验。)

Zul*_*lan 5

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

omp_set_lock(&taillock)
tail->next_record = r;
tail = r;
omp_unset_lock(&taillock)
Run Code Online (Sandbox Code Playgroud)

和某处:

omp_lock_t taillock;
omp_init_lock(&taillock);

...

omp_destroy_lock(&taillock);
Run Code Online (Sandbox Code Playgroud)

简单的 OpenMP 解决方案:

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

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

出于实际目的,使用 Pthread 锁也可以,至少对于 OpenMP 基于 Pthread 的场景来说是这样。

警告的话

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

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

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

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

开销和扰动

首先,区分两者(相关概念)。开销是您花费的额外时间,而扰动是指对您测量的内容的影响(即您现在测量的东西与没有测量时发生的情况不同)。大量的开销是不受欢迎的,但扰动会更糟。

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

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