测量 OpenMP Fork/Join 延迟

2024-04-18

由于 MPI-3 具有共享内存并行功能,并且它似乎与我的应用程序完美匹配,因此我正在认真考虑将我的混合 OpemMP-MPI 代码重写为纯 MPI 实现。

为了给棺材里钉上最后一颗钉子,我决定运行一个小程序来测试 OpenMP fork/join 机制的延迟。这是代码(为英特尔编译器编写):

void action1(std::vector<double>& t1, std::vector<double>& t2)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] = std::sin(t2.data()[index]) * std::cos(t2.data()[index]);
    }
}

void action2(std::vector<double>& t1, std::vector<double>& t2)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] = t2.data()[index] * std::sin(t2.data()[index]);
    }
}

void action3(std::vector<double>& t1, std::vector<double>& t2)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] = t2.data()[index] * t2.data()[index];
    }
}

void action4(std::vector<double>& t1, std::vector<double>& t2)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] = std::sqrt(t2.data()[index]);
    }
}

void action5(std::vector<double>& t1, std::vector<double>& t2)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] = t2.data()[index] * 2.0;
    }
}

void all_actions(std::vector<double>& t1, std::vector<double>& t2)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] = std::sin(t2.data()[index]) * std::cos(t2.data()[index]);
        t1.data()[index] = t2.data()[index] * std::sin(t2.data()[index]);
        t1.data()[index] = t2.data()[index] * t2.data()[index];
        t1.data()[index] = std::sqrt(t2.data()[index]);
        t1.data()[index] = t2.data()[index] * 2.0;
    }
}


int main()
{
    // decide the process parameters
    const auto n = std::size_t{8000000};
    const auto test_count = std::size_t{500};
    
    // garbage data...
    auto t1 = std::vector<double>(n);
    auto t2 = std::vector<double>(n);
    
    //+/////////////////
    // perform actions one after the other
    //+/////////////////
    
    const auto sp = timer::spot_timer();
    const auto dur1 = sp.duration_in_us();
    for (auto index = std::size_t{}; index < test_count; ++index)
    {
        #pragma noinline
        action1(t1, t2);
        #pragma noinline
        action2(t1, t2);
        #pragma noinline
        action3(t1, t2);
        #pragma noinline
        action4(t1, t2);
        #pragma noinline
        action5(t1, t2);
    }
    const auto dur2 = sp.duration_in_us();
    
    //+/////////////////
    // perform all actions at once
    //+/////////////////
    const auto dur3 = sp.duration_in_us();
    for (auto index = std::size_t{}; index < test_count; ++index)
    {
        #pragma noinline
        all_actions(t1, t2);
    }
    const auto dur4 = sp.duration_in_us();
    
    const auto a = dur2 - dur1;
    const auto b = dur4 - dur3;
    if (a < b)
    {
        throw std::logic_error("negative_latency_error");
    }
    const auto fork_join_latency = (a - b) / (test_count * 4);
    
    // report
    std::cout << "Ran the program with " << omp_get_max_threads() << ", the calculated fork/join latency is: " << fork_join_latency << " us" << std::endl;
    
    return 0;
}

正如您所看到的,其想法是单独执行一组操作(每个操作都在一个 OpenMP 循环内)并计算其平均持续时间,然后一起执行所有这些操作(在同一个 OpenMP 循环内)并计算的平均持续时间。然后我们有一个两个变量的线性方程组,其中之一是 fork/join 机制的延迟,可以求解该方程以获得该值。

问题:

  1. 我是否忽略了什么?
  2. 目前,我正在使用“-O0”来阻止 smarty-pants 编译器执行其有趣的操作。我应该使用哪种编译器优化,这些优化也会对延迟本身等产生影响吗?
  3. 在我的 6 核 Coffee Lake 处理器上,我测得延迟约为 850 us。这听起来正确吗?

Edit 3

  1. )根据 @paleonix 的建议,我在一开始就加入了热身计算,

  2. )为了简单起见,我减少了操作的数量,并且,

  3. )我已切换到“omp_get_wtime”以使其易于理解。

我现在使用标志 -O3 运行以下代码:

void action1(std::vector<double>& t1)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] = std::sin(t1.data()[index]);
    }
}

void action2(std::vector<double>& t1)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] =  std::cos(t1.data()[index]);
    }
}

void action3(std::vector<double>& t1)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        t1.data()[index] = std::atan(t1.data()[index]);
    }
}

void all_actions(std::vector<double>& t1, std::vector<double>& t2, std::vector<double>& t3)
{
    #pragma omp parallel for schedule(static) num_threads(std::thread::hardware_concurrency())
    for (auto index = std::size_t{}; index < t1.size(); ++index)
    {
        #pragma optimize("", off)
        t1.data()[index] = std::sin(t1.data()[index]);
        t2.data()[index] = std::cos(t2.data()[index]);
        t3.data()[index] = std::atan(t3.data()[index]);
        #pragma optimize("", on)
    }
}


int main()
{
    // decide the process parameters
    const auto n = std::size_t{1500000}; // 12 MB (way too big for any cache)
    const auto experiment_count = std::size_t{1000};
    
    // garbage data...
    auto t1 = std::vector<double>(n);
    auto t2 = std::vector<double>(n);
    auto t3 = std::vector<double>(n);
    auto t4 = std::vector<double>(n);
    auto t5 = std::vector<double>(n);
    auto t6 = std::vector<double>(n);
    auto t7 = std::vector<double>(n);
    auto t8 = std::vector<double>(n);
    auto t9 = std::vector<double>(n);
    
    //+/////////////////
    // warum-up, initialization of threads etc.
    //+/////////////////
    for (auto index = std::size_t{}; index < experiment_count / 10; ++index)
    {
        all_actions(t1, t2, t3);
    }
    
    //+/////////////////
    // perform actions (part A)
    //+/////////////////
    
    const auto dur1 = omp_get_wtime();
    for (auto index = std::size_t{}; index < experiment_count; ++index)
    {
        action1(t4);
        action2(t5);
        action3(t6);
    }
    const auto dur2 = omp_get_wtime();
    
    //+/////////////////
    // perform all actions at once (part B)
    //+/////////////////

    const auto dur3 = omp_get_wtime();
    #pragma nofusion
    for (auto index = std::size_t{}; index < experiment_count; ++index)
    {
        all_actions(t7, t8, t9);
    }
    const auto dur4 = omp_get_wtime();
    
    const auto a = dur2 - dur1;
    const auto b = dur4 - dur3;
    const auto fork_join_latency = (a - b) / (experiment_count * 2);
    
    // report
    std::cout << "Ran the program with " << omp_get_max_threads() << ", the calculated fork/join latency is: "
        << fork_join_latency * 1E+6 << " us" << std::endl;
    
    return 0;
}

这样,测得的延迟现在为 115 us。现在令我困惑的是这个值changes当动作改变时。根据我的逻辑,由于我在 A 部分和 B 部分中执行相同的操作,因此实际上应该没有任何变化。为什么会发生这种情况?


这是我测量 fork-join 开销的尝试:

#include <iostream>
#include <string>

#include <omp.h>

constexpr int n_warmup = 10'000;
constexpr int n_measurement = 100'000;
constexpr int n_spins = 1'000;

void spin() {
    volatile bool flag = false;
    for (int i = 0; i < n_spins; ++i) {
        if (flag) {
            break;
        }
    }
}

void bench_fork_join(int num_threads) {
    omp_set_num_threads(num_threads);

    // create threads, warmup
    for (int i = 0; i < n_warmup; ++i) {
        #pragma omp parallel
        spin();
    }

    double const start = omp_get_wtime();
    for (int i = 0; i < n_measurement; ++i) {
        #pragma omp parallel
        spin();
    }
    double const stop = omp_get_wtime();
    double const ptime = (stop - start) * 1e6 / n_measurement;

    // warmup
    for (int i = 0; i < n_warmup; ++i) {
        spin();
    }
    double const sstart = omp_get_wtime();
    for (int i = 0; i < n_measurement; ++i) {
        spin();
    }
    double const sstop = omp_get_wtime();
    double const stime = (sstop - sstart) * 1e6 / n_measurement;

    std::cout << ptime << " us\t- " << stime << " us\t= " << ptime - stime << " us\n";
}

int main(int argc, char **argv) {
    auto const params = argc - 1;
    std::cout << "parallel\t- sequential\t= overhead\n";

    for (int j = 0; j < params; ++j) {
        auto num_threads = std::stoi(argv[1 + j]);
        std::cout << "---------------- num_threads = " << num_threads << " ----------------\n";
        bench_fork_join(num_threads);
    }

    return 0;
}

您可以使用多个不同数量的线程来调用它,这些线程数量不应高于计算机上的核心数量才能给出合理的结果。在我的 6 核机器上并使用 gcc 11.2 编译时,我得到

$ g++ -fopenmp -O3 -DNDEBUG -o bench-omp-fork-join bench-omp-fork-join.cpp
$ ./bench-omp-fork-join 6 4 2 1
parallel        - sequential    = overhead
---------------- num_threads = 6 ----------------
1.51439 us      - 0.273195 us   = 1.24119 us
---------------- num_threads = 4 ----------------
1.24683 us      - 0.276122 us   = 0.970708 us
---------------- num_threads = 2 ----------------
1.10637 us      - 0.270865 us   = 0.835501 us
---------------- num_threads = 1 ----------------
0.708679 us     - 0.269508 us   = 0.439171 us

每行中的第一个数字是有线程的平均值(超过 100,000 次迭代),第二个数字是没有线程的平均值。最后一个数字是前两个数字之间的差值,并且应该是 fork-join 开销的上限。

确保每行中间列(无线程)中的数字大致相同,因为它们应该独立于线程数。如果不是,请确保计算机上没有运行任何其他内容和/或增加测量和/或预热运行的次数。

关于将 OpenMP 替换为 MPI,请记住 MPI 仍然是多处理而不是多线程。您可能会付出大量内存开销,因为进程往往比线程大得多。

EDIT:

修改了基准,使用在易失性标志上旋转而不是休眠(感谢@Jérôme Richard)。正如 Jérôme Richard 在他的回答中提到的,测量的开销随着n_spins。环境n_spins低于 1000 并没有显着改变我的测量结果,所以这就是我测量的位置。正如上面所看到的,测量的开销远低于基准测试的早期版本测量的开销。

睡眠的不准确是一个问题,特别是因为人们总是会测量睡眠时间最长的线程,因此会偏向更长的时间,即使睡眠时间本身会围绕输入时间对称分布。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

测量 OpenMP Fork/Join 延迟 的相关文章

随机推荐

  • 更改my.ini后MySQL服务无法启动

    我在 Windows 上运行 MySQL 8 0 我对 my ini 做了一些更改 最终找到了它 以更改字符集 愚蠢的是我没有先备份它 并且我使用了记事本 它没有多重撤消功能 现在我明白了 The MySQL Service could n
  • Kivy 中的 HTTPS 请求

    我一直在处理与通过 AWS API Gateway 托管的 API 绑定的 Kivy 应用程序中的 HTTPS 请求 首先 我从Python3迁移到2 然后从requests库迁移到kivy的URLRequest 该应用程序在我的 Linu
  • prawnto 显示新页面时不会中断的表格

    我有数量可变的表 行数可变 我想让它们一个接一个地显示 但如果当前页面不适合表 请将其放在下一页上 然后继续 我已将表格放入事务中 这样如果高度适合当前页面 我可以回滚然后打印它 但如何获取表格高度 我现在有这个代码 pdf transac
  • 将 CSV 导入组织模式属性

    我想将 CSV 导入组织模式 其他人已经询问过如何将 CSV 导入组织模式表 这不是我想做的 我需要将 CSV 导入到组织模式属性 例如 像这样的 CSV Name Tel Mobile Fax John 11111 22222 33333
  • 删除具有重复索引的 pandas 行

    如何删除具有重复索引值的行 在下面的天气数据框中 有时科学家会返回并纠正观察结果 不是通过编辑错误的行 而是通过将重复的行附加到文件末尾 我正在从网络上读取一些自动天气数据 每 5 分钟进行一次观测 并编译成每个气象站的每月文件 解析文件后
  • 使用 ffmpeg 将文件从一种格式转换为另一种格式

    我是新来的ffmpeg我试图找出如何将音频或视频文件从一种格式转换为另一种格式 我不想使用CLI 我只是想知道我是否可以使用ffmpeg作为库并调用函数将文件从一种格式转换为另一种格式 我浏览了文档并找到了函数avcodec encode
  • 如何仅在夹具级别执行“beforeEach”,而不是针对该夹具下的每个测试

    我只想在固定装置级别运行 beforeEach 而不是在该固定装置下的每个测试中运行 fixture Fixture A for Use Case1 beforeEach login test A Test 1 async t gt awa
  • 这是 Matlab 的错误吗?你有同样的问题吗? [复制]

    这个问题在这里已经有答案了 我的Matlab版本是R2012a为什么在Matlab中1 1 0 2不等于0 9 这太糟糕了 gt gt 1 1 0 2 0 9 ans 0 这不是Matlab问题 这是一个浮点问题 在 C 或任何符合以下标准
  • 使用 Google Drive API 从 Google Drive 直接下载

    我的桌面应用程序是用 java 编写的 尝试从 Google Drive 下载公共文件 据我发现 它可以通过使用文件来实现webContentLink 这是为了能够在未经用户授权的情况下下载公共文件 因此 下面的代码适用于小文件 Strin
  • 如何将两个或多个不同 csv 文件组成的数据框中的两列合并为一个新列?

    我有几个 csv 文件 全部以日期命名 对于所有文件 我想在每个文件中创建一个新列 其中包含来自其他两列放在一起的数据 然后 我想将它们组合成一个大数据框 并仅选择其中两列来保留 这是一个例子 假设我有两个数据框 a b c a b c x
  • Rails 的 javascript_include_tag 可以忽略之前加载的脚本吗?

    我正在使用这条线 javascript include tag all recursive gt true cache gt true 在 Rails 应用程序的页脚中执行以下操作 递归加载public javascripts下的所有脚本
  • 传递给子指令时父指令控制器未定义

    我在这里问了一般性问题这个帖子 https stackoverflow com questions 42814530 pass argument between parent and child directives 我已经通过工作示例得到
  • 将 RavenDB 与 ServiceStack 结合使用

    I read this http www philliphaydon com 2012 06 using nhibernate with servicestack Phillip Haydon 发表的有关如何将 NHibernate Rav
  • ndk-build DUMP_APP_ABI 在 Windows 上返回 2 行

    我无法在 Windows 上调试 android ndk 应用程序 它seems https stackoverflow com questions 20047348 unknown application abi while debug
  • MySQLdb 使用列表作为输入执行许多?

    我想在我的程序中使用executemany一次存储20条记录 这就是文档中所说的 c executemany INSERT INTO breakfast name spam eggs sausage price VALUES s s s s
  • 为什么实体框架在 SELECT 上生成 JOIN

    我在 C 应用程序中使用实体框架 并且使用延迟加载 我们注意到一个查询对我们的 CPU 有着极高的影响 它仅仅计算一个总和 调试实体框架生成的查询时 它会创建一个INNER JOIN SELECT 这不是高性能的 当我手动将查询更改为正确的
  • magento 付款流程..一般如何运作

    有一个问题 我希望这是问的正确地方 不太明白magento 中的付款方式 客户去结账 假设想要以客人身份付款 因此提供地址等 最后找到付款方式 然后我希望客户通过信用卡付款 已经为我选择的网关 银行 安装了模块 那时 我希望用户被重定向到第
  • Mysql 变量无法通过 php mysql 查询工作

    我有这样的疑问 query SET points 1 SET num 0 SELECT id rank num if points rank num num 1 as point rank FROM said ORDER BY rank 1
  • 调用静态方法时发生致命错误

    所以 这是我的情况 我正在使用 CodeIgniter 我已经设置了一个助手 DK 文件夹下的 string helper 我已经在 dk string helper php 中设置了 dkString 类 static function
  • 测量 OpenMP Fork/Join 延迟

    由于 MPI 3 具有共享内存并行功能 并且它似乎与我的应用程序完美匹配 因此我正在认真考虑将我的混合 OpemMP MPI 代码重写为纯 MPI 实现 为了给棺材里钉上最后一颗钉子 我决定运行一个小程序来测试 OpenMP fork jo