使用英特尔 OpenMP 找到最佳线程数:只有 1 个线程比许多线程的结果更好
Find the best number of thread with Intel OpenMP : only 1 thread has better results than many threads
在我的代码中多次使用以下类型的循环:
#pragma omp parallel for schedule(dynamic, num_threads)
for(int i=0; i<F_matrix_A.size(); i++){
for(int j=0; j<F_matrix_A.size(); j++){
F_previous_T[i][j] = F_previous[j][i];
}
}
#pragma omp parallel for schedule(dynamic, num_threads)
for(int i=0; i<F_matrix_A.size(); i++){
for(int k=0; k<F_matrix_A.size(); k++){
for(int j=0; j<=i; j++){
if(F_previous_T[i][k] != 0 && F_previous[k][j] !=0){
Fisher_new[i][j] += F_previous_T[i][k]*F_previous[k][j];
}
}
}
}
当我在参数之前设置时,我得到了最好的性能:#define num_threads 1
我正在一个有 64 个内核的工作站上工作(当我执行 /proc/cpuinfo
时,我看到有 128 个处理器)。没能从这么多进程中受益,我觉得很遗憾。
是不是因为我使用了特定的pragma:
#pragma omp parallel for schedule(dynamic, num_threads)
??
是否有其他方法可以缩短 运行时间?我在不同的论坛上看到,使用大量进程可能会导致大量开销。
我的循环的大小通常为 1700x1700。
如果有人有想法,可以告诉它。
更新 1 : 我有 2 个版本的代码,一个带有 GNU g++
,另一个带有 Intel icpc
1) 我正在使用 Makefile 之后的 "generic" :
ifneq "$(MAKECMDGOALS)" "clean"
include $(MAKECMDGOALS).make
endif
OBJECTS = $(SOURCES:.cpp=.o)
$(MAKECMDGOALS): $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CXX) $(LDFLAGS) $(OBJECTS) -o $@
.cpp.o:
$(CXX) $(CXXFLAGS) $(LDFLAGS) $< -o $@
clean:
rm -f *.o
1) 对于 GNU g++
,我用 gnu.make
文件编译:
CXX = g++ -std=c++11 -O3 -fopenmp
CXXFLAGS = -Wall -c
LDFLAGS = -march=native
LDFLAGS =
SOURCES = main.cpp TSAF_gnu.cpp
EXECUTABLE = main_gnu.exe
2) 对于 Intel icpc
,我用 intel.make
文件编译:
CXX = icpc -std=c++11 -O3 -xHost -qopenmp
CXXFLAGS = -Wall -c -I${MKLROOT}/include
LDFLAGS = -mkl=parallel
LDFLAGS += -L${MKLROOT}/lib/intel64_lin -Wl,-rpath,${MKLROOT}/lib/intel64_lin -lmkl_intel_lp64 -lmkl_intel_thread \
-lmkl_core -liomp5 -lpthread
SOURCES = main.cpp TSAF_intel.cpp
EXECUTABLE = main_intel.exe
一个标准的运行大约需要3分钟。
行 schedule(dynamic, num_threads)
可能会导致 可扩展性问题 。
确实,对于大小为 1700 和 64 个线程的矩阵,动态调度策略的块大小为 64。因此,块的数量为 floor(1700/64) = 26
,这太小而无法满足 64 个线程的需求!
即使有 32 个线程,工作平衡 也不是很好。我认为每个线程至少有 3-4 个块很重要。
随着线程数增加 粒度 很奇怪。根据输入大小设置粒度可能更相关。我建议使用 schedule(guided)
或 schedule(dynamic,chunksize)
并将块大小设置为 max(F_matrix_A.size() / (num_threads * 4), 1)
之类的东西(尽管如果不添加 [=17,则使用 schedule(dynamic,1)
应该不会太糟糕=]).
或者,您可以使用 task 和 taskloops 指令。
另请注意,如果您在具有多个 NUMA 节点的机器上工作(这可能是因为有 64 个内核),您应该非常小心 动态调度因为线程可能访问远程NUMA内存节点显着降低性能(这显然是你做的不是 想要你的记忆绑定代码)。
更新:您可以同时处理数组的两个垂直边,以显着减少内循环计算时间的可变性。结果将是这样的:
#pragma omp parallel for schedule(static)
for(int i=0; i<(F_matrix_A.size()+1)/2; i++)
{
// Upper-part
for(int k=0; k<F_matrix_A.size(); k++)
for(int j=0; j<=i; j++)
if(F_previous_T[i][k] != 0 && F_previous[k][j] != 0)
Fisher_new[i][j] += F_previous_T[i][k]*F_previous[k][j];
// Lower-part (do not perform the middle twice)
if(i < F_matrix_A.size()/2)
{
const int i2 = F_matrix_A.size() - 1 - i;
for(int k=0; k<F_matrix_A.size(); k++)
for(int j=0; j<=i2; j++)
if(F_previous_T[i2][k] != 0 && F_previous[k][j] != 0)
Fisher_new[i2][j] += F_previous_T[i2][k]*F_previous[k][j];
}
}
在我的代码中多次使用以下类型的循环:
#pragma omp parallel for schedule(dynamic, num_threads)
for(int i=0; i<F_matrix_A.size(); i++){
for(int j=0; j<F_matrix_A.size(); j++){
F_previous_T[i][j] = F_previous[j][i];
}
}
#pragma omp parallel for schedule(dynamic, num_threads)
for(int i=0; i<F_matrix_A.size(); i++){
for(int k=0; k<F_matrix_A.size(); k++){
for(int j=0; j<=i; j++){
if(F_previous_T[i][k] != 0 && F_previous[k][j] !=0){
Fisher_new[i][j] += F_previous_T[i][k]*F_previous[k][j];
}
}
}
}
当我在参数之前设置时,我得到了最好的性能:#define num_threads 1
我正在一个有 64 个内核的工作站上工作(当我执行 /proc/cpuinfo
时,我看到有 128 个处理器)。没能从这么多进程中受益,我觉得很遗憾。
是不是因为我使用了特定的pragma:
#pragma omp parallel for schedule(dynamic, num_threads)
??
是否有其他方法可以缩短 运行时间?我在不同的论坛上看到,使用大量进程可能会导致大量开销。
我的循环的大小通常为 1700x1700。
如果有人有想法,可以告诉它。
更新 1 : 我有 2 个版本的代码,一个带有 GNU g++
,另一个带有 Intel icpc
1) 我正在使用 Makefile 之后的 "generic" :
ifneq "$(MAKECMDGOALS)" "clean"
include $(MAKECMDGOALS).make
endif
OBJECTS = $(SOURCES:.cpp=.o)
$(MAKECMDGOALS): $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CXX) $(LDFLAGS) $(OBJECTS) -o $@
.cpp.o:
$(CXX) $(CXXFLAGS) $(LDFLAGS) $< -o $@
clean:
rm -f *.o
1) 对于 GNU g++
,我用 gnu.make
文件编译:
CXX = g++ -std=c++11 -O3 -fopenmp
CXXFLAGS = -Wall -c
LDFLAGS = -march=native
LDFLAGS =
SOURCES = main.cpp TSAF_gnu.cpp
EXECUTABLE = main_gnu.exe
2) 对于 Intel icpc
,我用 intel.make
文件编译:
CXX = icpc -std=c++11 -O3 -xHost -qopenmp
CXXFLAGS = -Wall -c -I${MKLROOT}/include
LDFLAGS = -mkl=parallel
LDFLAGS += -L${MKLROOT}/lib/intel64_lin -Wl,-rpath,${MKLROOT}/lib/intel64_lin -lmkl_intel_lp64 -lmkl_intel_thread \
-lmkl_core -liomp5 -lpthread
SOURCES = main.cpp TSAF_intel.cpp
EXECUTABLE = main_intel.exe
一个标准的运行大约需要3分钟。
行 schedule(dynamic, num_threads)
可能会导致 可扩展性问题 。
确实,对于大小为 1700 和 64 个线程的矩阵,动态调度策略的块大小为 64。因此,块的数量为 floor(1700/64) = 26
,这太小而无法满足 64 个线程的需求!
即使有 32 个线程,工作平衡 也不是很好。我认为每个线程至少有 3-4 个块很重要。
随着线程数增加 粒度 很奇怪。根据输入大小设置粒度可能更相关。我建议使用 schedule(guided)
或 schedule(dynamic,chunksize)
并将块大小设置为 max(F_matrix_A.size() / (num_threads * 4), 1)
之类的东西(尽管如果不添加 [=17,则使用 schedule(dynamic,1)
应该不会太糟糕=]).
或者,您可以使用 task 和 taskloops 指令。
另请注意,如果您在具有多个 NUMA 节点的机器上工作(这可能是因为有 64 个内核),您应该非常小心 动态调度因为线程可能访问远程NUMA内存节点显着降低性能(这显然是你做的不是 想要你的记忆绑定代码)。
更新:您可以同时处理数组的两个垂直边,以显着减少内循环计算时间的可变性。结果将是这样的:
#pragma omp parallel for schedule(static)
for(int i=0; i<(F_matrix_A.size()+1)/2; i++)
{
// Upper-part
for(int k=0; k<F_matrix_A.size(); k++)
for(int j=0; j<=i; j++)
if(F_previous_T[i][k] != 0 && F_previous[k][j] != 0)
Fisher_new[i][j] += F_previous_T[i][k]*F_previous[k][j];
// Lower-part (do not perform the middle twice)
if(i < F_matrix_A.size()/2)
{
const int i2 = F_matrix_A.size() - 1 - i;
for(int k=0; k<F_matrix_A.size(); k++)
for(int j=0; j<=i2; j++)
if(F_previous_T[i2][k] != 0 && F_previous[k][j] != 0)
Fisher_new[i2][j] += F_previous_T[i2][k]*F_previous[k][j];
}
}