【问题标题】:Using OpenMP multithread is much slower than single thread使用 OpenMP 多线程比单线程慢得多
【发布时间】:2021-07-13 21:15:47
【问题描述】:

我正在尝试使用 OpenMP 并行我的 C++ 神经网络训练过程。但这行不通。
然后我使用带有嵌套循环的简单 C++ 代码来测试 OpenMP。
但是 OpenMP 多线程比单线程慢得多。
我做错了什么让它变慢了吗?还是我错过了什么?

系统

MacOS 4 核

语言

C++

时间函数

我同时使用了 high_resolution_clock::now()omp_get_wtime()

  1. std::chrono::high_resolution_clock::now();

单线程花费时间:0.00000000000000
2 个线程花费时间:0.00010013580322
4 个线程花费时间:0.00016403198242
6 个线程花费时间:0.00017309188843
8 个线程花费时间:0.00112605094910
10 个线程花费时间:0.00013613700867
12个线程花费时间:0.00082898139954

  1. omp_get_wtime();

单线程花费时间:0.00000005900000
2 个线程花费时间:0.00009907600000
4 个线程花费时间:0.00018207300000
6 个线程花费时间:0.00014479500000
8 个线程花费时间:0.00070604400000
10 个线程花费时间:0.00057277700000
12个线程花费时间:0.00074358000000

代码

#include <iostream>
#include <omp.h>
#include <chrono>
#include <iomanip>

using namespace std;
void test() {
    int j = 0;
    for (int i = 0; i < 100000; i++) {
        // do something to kill time...
        j++;
    }
};

int main()
{
    auto startTime = chrono::high_resolution_clock::now();
    auto endTime = chrono::high_resolution_clock::now();

    // without openMp
    startTime = chrono::high_resolution_clock::now();
    for (int i = 0; i < 100000; i++) {
        test();
    }
    endTime = chrono::high_resolution_clock::now();
    chrono::duration<double> diff = endTime - startTime;
    cout << setprecision(14) << fixed;
    cout << "single thread cost time: " << diff.count() << endl;

    // 2 threads
    startTime = chrono::high_resolution_clock::now();
    #pragma omp parallel for num_threads(2)
    for (int i = 0; i < 100000; i++) {
        test();
    }
    endTime = chrono::high_resolution_clock::now();
    diff = endTime - startTime;
    cout << "2 threads cost time: " << diff.count() << endl;

    // 4 threads
    startTime = chrono::high_resolution_clock::now();
    #pragma omp parallel for num_threads(4)
    for (int i = 0; i < 100000; i++) {
        test();
    }
    endTime = chrono::high_resolution_clock::now();
    diff = endTime - startTime;
    cout << "4 threads cost time: " << diff.count() << endl;

    // 6 threads
    startTime = chrono::high_resolution_clock::now();
    #pragma omp parallel for num_threads(6)
    for (int i = 0; i < 100000; i++) {
        test();
    }
    endTime = chrono::high_resolution_clock::now();
    diff = endTime - startTime;
    cout << "6 threads cost time: " << diff.count() << endl;

    startTime = chrono::high_resolution_clock::now();
    #pragma omp parallel for num_threads(8)
    for (int i = 0; i < 100000; i++) {
        test();
    }
    endTime = chrono::high_resolution_clock::now();
    diff = endTime - startTime;
    cout << "8 threads cost time: " << diff.count() << endl;

    startTime = chrono::high_resolution_clock::now();
    #pragma omp parallel for num_threads(10)
    for (int i = 0; i < 100000; i++) {
        test();
    }
    endTime = chrono::high_resolution_clock::now();
    diff = endTime - startTime;
    cout << "10 threads cost time: " << diff.count() << endl;

    startTime = chrono::high_resolution_clock::now();
    #pragma omp parallel for num_threads(12)
    for (int i = 0; i < 100000; i++) {
        test();
    }
    endTime = chrono::high_resolution_clock::now();
    diff = endTime - startTime;
    cout << "12 threads cost time: " << diff.count() << endl;

    // system("pause");
    return 0;
}

我如何编译代码

clang++ -std=c++11 -Xpreprocessor -fopenmp parallel.cpp -O3 -o parallel -lomp

更新

大家好,前面的问题已经解决了,我想我不应该使用NUM_THREAD。

但是当我使用 OpenMP 来加速我的神经网络时,需要更长的时间。

数据大小

MNIST 数据集,每个 epoch 60000

时间函数

omp_get_wtime()

单线程结果

***** 训练纪元 1.
批数:6000。
批量大小:10。
进度:5999/6000。
火车时间是... 64.7082。
准确度:97.72% 9772/10000。
预测时间是... 3.51836。
发布数据样本...
释放神经网络...

OpenMP 的结果

***** 训练纪元 1.
批数:6000。
批量大小:10。
进度:5999/6000。
火车时间是:247.615.
准确度:97.72% 9772/10000。
预测时间为:30.739。

使用并行的代码

#pragma omp parallel for
        for (int k = 0; k < size; k++) {
            layer->map[i].data[k] = activation_func::tan_h(layer->map_common[k] + layer->map[i].b);
            // cout << "current thread: " << omp_get_thread_num() << endl;
        }

使用 parallel foromp critical 的代码

for (int k = 0; k < layer->map_count; k++) {
        for (int i = 0; i < map_h; i++) {
            for (int j = 0; j < map_w; j++) {
                double max_value = prev_layer->map[k].data[2*i*upmap_w + 2*j];
                for (int n = 2*i; n < 2*(i + 1); n++) {
                    #pragma omp parallel for
                    for (int m = 2*j; m < 2*(j + 1); m++) {
                         #pragma omp critical
                        max_value = MAX(max_value, prev_layer->map[k].data[n*upmap_w + m]);
                    }
                }
                layer->map[k].data[i*map_w + j] = activation_func::tan_h(max_value);
            }
        }
    }

【问题讨论】:

  • 您的测试循环实际上并没有做任何事情,因此编译器可能正在删除它。那么你得到的时间主要是花在创建线程上的时间。
  • 测试函数应该返回值并且你的代码应该在某处打印它。正如@1201ProgramAlarm 所说,编译器可能会检测到您只是在浪费计算时间并删除循环。
  • 9个女人一个月不能生孩子!您的计算时间太小,因此多线程很有用。创建/结束线程需要时间。此外,请使用环境变量OMP_NUM_THREADS 而不是num_threads(...)
  • @JérômeRichard 关于最后一句话,不确定它是否会起作用,因为 OP 有多个具有不同线程数的并行区域
  • @dreamcrash 是的,但我认为最好在专用脚本(例如 bash)中移除(基于复制过去的)基准测试代码。无论如何,这在这里并不重要,因为该程序是一个基准。

标签: c++ multithreading macos parallel-processing openmp


【解决方案1】:

我正在尝试使用并行我的 C++ 神经网络训练过程 开放MP。但它不会起作用。然后我使用了一个简单的 C++ 代码 用于测试 OpenMP 的嵌套循环。

我经常看到这种情况; 在代码中引入 OpenMP 或并行性不会神奇地让您的代码更快。

为什么?由于很多因素,但是(在您的上下文中)因为并行完成的工作应该足够大以克服并行性的开销(例如线程创建、同步等)。为此,您需要增加并行任务的大小/数量。

另一个问题是您对代码进行基准测试的方式:

你的并行任务:

void test() {
    int j = 0;
    for (int i = 0; i < 100000; i++) {
        // do something to kill time...
        j++; <---- Not enough work done in parallel 
    }
};

在顺序版本中,编译器可以轻松扣除j = 100000 - 1;。此外,因为您没有对该值做任何事情( j),编译器实际上可以优化对test() 函数的整个调用。因此,正如 cmets 中指出的那样:

您的测试循环实际上并没有做任何事情,因此编译器可能是 删除它。 那么你得到的时间将主要是花费的时间 创建线程。 - 1201ProgramAlarm

测试函数应该返回值并且你的代码应该打印 它在某处。 正如@1201ProgramAlarm 所说,编译器可能会检测到 你只是在浪费计算时间并删除循环。 - Michael 克莱姆

此外,没有以下代码块:

// 2 threads
startTime = chrono::high_resolution_clock::now();
#pragma omp parallel for num_threads(2)
for (int i = 0; i < 100000; i++) {
    test();
}
endTime = chrono::high_resolution_clock::now();
diff = endTime - startTime;
cout << "2 threads cost time: " << diff.count() << endl;

复制了很多次,最好只用一次,并从外部使用环境变量OMP_NUM_THREADS更改线程数。

关于您的更新:

for (int k = 0; k < layer->map_count; k++) {
        for (int i = 0; i < map_h; i++) {
            for (int j = 0; j < map_w; j++) {
                double max_value = prev_layer->map[k].data[2*i*upmap_w + 2*j];
                for (int n = 2*i; n < 2*(i + 1); n++) {
                    #pragma omp parallel for
                    for (int m = 2*j; m < 2*(j + 1); m++) {
                         #pragma omp critical
                        max_value = MAX(max_value, prev_layer->map[k].data[n*upmap_w + m]);
                    }
                }
                layer->map[k].data[i*map_w + j] = activation_func::tan_h(max_value);
            }
        }
    }

critical section 基本上是在使代码按顺序排列。实际上比顺序更糟糕,因为存在锁定机制的额外开销。

您应该使用 OpenMP reduce 而不是 #pragma omp critical,这正是针对这种情况的意思。此外,您可以尝试并行化 for (int n = 2*i; n &lt; 2*(i + 1); n++)

for (int k = 0; k < layer->map_count; k++) {
        for (int i = 0; i < map_h; i++) {
            for (int j = 0; j < map_w; j++) {
                double max_value = prev_layer->map[k].data[2*i*upmap_w + 2*j];
                #pragma omp parallel for reduction(max: max_value)
                for (int n = 2*i; n < 2*(i + 1); n++) {
                    for (int m = 2*j; m < 2*(j + 1); m++) {
                        max_value = MAX(max_value, prev_layer->map[k].data[n*upmap_w + m]);
                    }
                }
                layer->map[k].data[i*map_w + j] = activation_func::tan_h(max_value);
            }
        }
    }

  1. 附注,就个人而言,不要以错误的方式理解它,但我认为在尝试盲目并行化代码之前,您应该先花更多时间学习多线程和 OpenMP 的基础知识。

  2. 请不要继续为原始问题添加更新,更新问题。只需创建一个新问题即可。

【讨论】:

    猜你喜欢
    • 2021-03-27
    • 2012-09-05
    • 1970-01-01
    • 2017-11-08
    • 1970-01-01
    • 2020-08-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多