使用英特尔 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) 应该不会太糟糕=]).

或者,您可以使用 tasktaskloops 指令。

另请注意,如果您在具有多个 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];
    }
}