我正在尝试在我的应用程序中检测一些函数,看看它们需要多长时间。我使用链接列表将所有时间记录在内存中。
在此过程中,我引入了一个全局变量来跟踪列表的末尾。当我输入新的计时区域时,我会在列表末尾插入一条新记录。相当简单的东西。
但是,我想要跟踪的一些函数是在 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 锁:
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;
}
这会创建一个绑定(bind)到源代码行的隐式全局锁。有关一些详细讨论,请参阅 this question 的答案.
出于实际目的,使用 Pthread 锁也可以,至少对于 OpenMP 基于 Pthread 的场景来说是这样。
警告一句话
在性能测量代码中使用锁是危险的。内存分配也是如此,这通常也意味着使用锁。这意味着 start_time
具有显着的成本,并且随着线程数量的增加,性能甚至会变得更差。这甚至没有考虑由于一个线程分配一 block 内存(记录)然后另一个线程修改它(尾指针)而导致的缓存失效。
现在,如果您测量的部分需要几秒钟,这可能没问题,但当您的部分只有数百个周期时,就会导致巨大的开销和扰动。
要创建可扩展的性能跟踪工具,您必须以更大的 block 预先分配线程本地内存,并让每个线程仅写入其本地部分。
您还可以选择使用一些现有的测量基础设施,例如 Score-P .
开销和扰动
首先,区分两者(相关概念)。 开销是您花费的额外时间,而扰动是指对您测量的内容的影响(即您现在测量的东西与没有测量时发生的情况不同)。大量的开销是不受欢迎的,但扰动会更糟。
是的,您可以通过在昂贵的测量运行时间期间暂停计时器来避免一些干扰(开销仍然存在)。然而,在多线程上下文中这仍然是一个很大的问题。
- 减慢一个线程的进度,可能会导致其他线程等待它,例如在隐式障碍期间。您如何归因该线程和其他传递性后续线程的等待时间?
- 内存分配通常是锁定的 - 因此,如果您在测量运行时分配内存,则会减慢依赖于内存分配的其他线程的速度。您可以尝试使用内存池来缓解,但我首先会避免使用链表。
关于c - 锁定 OMP 区域,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40956148/