我想读取输入文件(在C / C ++中)并尽可能快地独立处理每一行。处理本身需要几个滴答,所以我决定使用OpenMP线程。我有这段代码:
#pragma omp parallel num_threads(num_threads)
{
string line;
while (true) {
#pragma omp critical(input)
{
getline(f, line);
}
if (f.eof())
break;
process_line(line);
}
}
我的问题是,如何确定要使用的最佳线程数?理想情况下,我希望在运行时动态检测到这一点。我不理解parallel
的DYNAMIC计划选项,所以我不能说这是否会有所帮助。任何见解?
另外,我不确定如何“手动”确定最佳数字。我为我的具体应用尝试了各种数字。我原本认为top
报告的CPU使用率会有所帮助,但它不会(!)在我的情况下,CPU使用率始终保持在num_threads *(85-95)左右。但是,使用pv
来观察我正在读取输入的速度,我注意到最佳数字是2-5左右;在此之上,输入速度变小。所以我的问题是 - 为什么我在使用10个线程时会看到CPU使用率为850?这可能是由于OpenMP处理等待进入关键部分的线程的效率低下吗?
for NCPU in $(seq 1 20) ; do echo "NCPU=$NCPU" ; { pv -f -a my_input.gz | pigz -d -p 20 | { { sleep 60 ; PID=$(ps gx -o pid,comm | grep my_prog | sed "s/^ *//" | cut -d " " -f 1) ; USAGE=$(ps h -o "%cpu" $PID) ; kill -9 $PID ; sleep 1 ; echo "usage: $USAGE" >&2 ; } & cat ; } | ./my_prog -N $NCPU >/dev/null 2>/dev/null ; sleep 2 ; } 2>&1 | grep -v Killed ; done
NCPU = 1 [8.27MB /秒] 用法:98.4
NCPU = 2 [12.5MB / s]的 用法:196
NCPU = 3 [18.4MB / s]的 用法:294
NCPU = 4 [23.6MB / s]的 用法:393
NCPU = 5 [28.9MB / s]的 用法:491
NCPU = 6 [33.7MB / s]的 用法:589
NCPU = 7 [37.4MB / s]的 用法:688
NCPU = 8 [40.3MB / s]的 用法:785
NCPU = 9 [41.9MB / s]的 用法:884
NCPU = 10 [41.3MB / s]的 用法:979
NCPU = 11 [41.5MB / s]的 用法:1077
NCPU = 12 [42.5MB / s]的 用法:1176
NCPU = 13 [41.6MB / s]的 用法:1272
NCPU = 14 [42.6MB / s]的 用法:1370
NCPU = 15 [41.8MB / s]的 用法:1493
NCPU = 16 [40.7MB / s]的 用法:1593
NCPU = 17 [40.8MB / s]的 用法:1662
NCPU = 18 [39.3MB / s]的 用法:1763
NCPU = 19 [38.9MB / s]的 用法:1857
NCPU = 20 [37.7MB / s]的 用法:1957年
我的问题是我可以使用785 CPU使用率达到40MB / s,但也可以使用1662 CPU。那些额外的周期在哪里去?
EDIT2:感谢Lirik和John Dibling,我现在明白,我发现上面的时间令人费解的原因与I / O无关,而是与OpenMP实现关键部分的方式有关。我的直觉是,如果CS中有1个线程,10个线程等待进入,则第一个线程退出CS的那一刻,内核应该唤醒另一个线程并让它进入。时间建议否则:线程可以自己多次唤醒并发现CS占用了吗?这是线程库或内核的问题吗?
答案 0 :(得分:2)
“我想读取一个输入文件(在C / C ++中)并尽可能快地独立处理每一行。”
从文件读取会使您的应用程序I / O受限,因此单独读取部分所能达到的最大性能是以最大磁盘速度读取(在我的机器上读取的CPU时间小于10%)。这意味着如果您能够完全释放任何处理中的读取线程,则需要处理的时间少于剩余的CPU时间(使用我的计算机时为90%)。如果线路处理线程占用的时间超过剩余的CPU时间,那么您将无法跟上硬盘驱动器的步伐。
在这种情况下有几种选择:
“......处理本身需要几个滴答,所以我决定使用OpenMP线程”
这是一个好兆头,但它也意味着您的CPU利用率不会很高。正如约翰·迪布林所提到的,这是你可以优化你的表现的部分,最好是手工完成。通常,最好是排队每一行并让处理线程从队列中提取处理请求,直到您无需处理任何其他内容。后者也被称为生产者/消费者设计模式 - 并发计算中非常常见的模式。
为什么
之间存在差异
- (i)每个进程获取锁定,拉取数据,释放锁定,处理数据;和
- (ii)一个过程:拉取数据,获取锁定,排队,释放锁定,
- 其他:获取锁定,出列块,释放锁定,处理数据?
差别很小:在某种程度上,两者都代表消费者/生产者模式。在第一种情况下(i)您没有实际队列,但您可以将文件流视为您的生产者(队列),而Consumer是从流中读取的线程。在第二种情况(ii)中,您明确地实现了消费者/生产者模式,这种模式更健壮,可重用并为生产者提供更好的抽象。如果您决定使用多个“输入通道”,那么后一种情况会更好。
最后(也可能是最重要的),您可以使用lock-free queue与单个制作人和单个消费者,这将使(ii)比(i)更快地让您成为我/约束。使用无锁队列,您可以拉取数据,入队块和 dequque chunk 而无需锁定。
答案 1 :(得分:1)
你可以做的最好的事情就是亲自手动调整,重复测量 - 调整 - 比较周期。
用于处理数据集的最佳线程数在很大程度上取决于许多因素,其中最重要的是:
您可以尝试设计一种测量处理器吞吐量并在运行中进行调整的某种启发式方法,但这种方式往往比它的价值更麻烦。
通常,对于I / O绑定的任务,我通常从每个核心大约12个线程开始并从那里进行调整。对于CPU绑定的任务,我从每个核心大约4个线程开始,然后从那里开始。如果您真的想要优化处理器的使用,关键是“从那里开始”部分。
另外请记住,如果您真的想进行优化,则应该将此设置配置为可配置,因为部署此系统的每个系统都具有不同的特性。