生产者-消费者在超级兄弟与非超级兄弟之间共享内存位置的延迟和吞吐量成本是多少?

2024-02-05

单个进程中的两个不同线程可以share通过读取和/或写入来获取公共内存位置。

通常,这种(有意的)共享是使用原子操作来实现的lockx86 上的前缀,其成本众所周知lock前缀本身(即无竞争成本)以及缓存行时的额外一致性成本实际上共享(真实或false https://en.wikipedia.org/wiki/False_sharing共享)。

在这里,我对单线程的生产者-消费者成本感兴趣P写入内存位置,另一个线程`C 从内存位置读取,两者都使用plain读取和写入。

在同一插槽上的不同内核上执行此类操作时,与在最新 x86 内核上的同一物理内核上的同级超线程上执行时相比,该操作的延迟和吞吐量是多少。

在标题中,我使用术语“超级同级”来指代在同一核心的两个逻辑线程上运行的两个线程,使用术语“核心同级”来指代在不同物理核心上运行的两个线程的更常见情况。


好吧,我找不到任何权威来源,所以我想我自己尝试一下。

#include <pthread.h>
#include <sched.h>
#include <atomic>
#include <cstdint>
#include <iostream>


alignas(128) static uint64_t data[SIZE];
alignas(128) static std::atomic<unsigned> shared;
#ifdef EMPTY_PRODUCER
alignas(128) std::atomic<unsigned> unshared;
#endif
alignas(128) static std::atomic<bool> stop_producer;
alignas(128) static std::atomic<uint64_t> elapsed;

static inline uint64_t rdtsc()
{
    unsigned int l, h;
    __asm__ __volatile__ (
        "rdtsc"
        : "=a" (l), "=d" (h)
    );
    return ((uint64_t)h << 32) | l;
}

static void * consume(void *)
{
    uint64_t    value = 0;
    uint64_t    start = rdtsc();

    for (unsigned n = 0; n < LOOPS; ++n) {
        for (unsigned idx = 0; idx < SIZE; ++idx) {
            value += data[idx] + shared.load(std::memory_order_relaxed);
        }
    }

    elapsed = rdtsc() - start;
    return reinterpret_cast<void*>(value);
}

static void * produce(void *)
{
    do {
#ifdef EMPTY_PRODUCER
        unshared.store(0, std::memory_order_relaxed);
#else
        shared.store(0, std::memory_order_relaxed);
#enfid
    } while (!stop_producer);
    return nullptr;
}



int main()
{
    pthread_t consumerId, producerId;
    pthread_attr_t consumerAttrs, producerAttrs;
    cpu_set_t cpuset;

    for (unsigned idx = 0; idx < SIZE; ++idx) { data[idx] = 1; }
    shared = 0;
    stop_producer = false;

    pthread_attr_init(&consumerAttrs);
    CPU_ZERO(&cpuset);
    CPU_SET(CONSUMER_CPU, &cpuset);
    pthread_attr_setaffinity_np(&consumerAttrs, sizeof(cpuset), &cpuset);

    pthread_attr_init(&producerAttrs);
    CPU_ZERO(&cpuset);
    CPU_SET(PRODUCER_CPU, &cpuset);
    pthread_attr_setaffinity_np(&producerAttrs, sizeof(cpuset), &cpuset);

    pthread_create(&consumerId, &consumerAttrs, consume, NULL);
    pthread_create(&producerId, &producerAttrs, produce, NULL);

    pthread_attr_destroy(&consumerAttrs);
    pthread_attr_destroy(&producerAttrs);

    pthread_join(consumerId, NULL);
    stop_producer = true;
    pthread_join(producerId, NULL);

    std::cout <<"Elapsed cycles: " <<elapsed <<std::endl;
    return 0;
}

使用以下命令进行编译,替换定义:

gcc -std=c++11 -DCONSUMER_CPU=3 -DPRODUCER_CPU=0 -DSIZE=131072 -DLOOPS=8000 timing.cxx -lstdc++ -lpthread -O2 -o timing

Where:

  • CONSUMER_CPU 是运行消费者线程的 cpu 编号。
  • PRODUCER_CPU 是运行生产者线程的 cpu 编号。
  • SIZE 是内部循环的大小(与缓存有关)
  • LOOPS 是,嗯...

以下是生成的循环:

消费者线程

  400cc8:       ba 80 24 60 00          mov    $0x602480,%edx
  400ccd:       0f 1f 00                nopl   (%rax)
  400cd0:       8b 05 2a 17 20 00       mov    0x20172a(%rip),%eax        # 602400 <shared>
  400cd6:       48 83 c2 08             add    $0x8,%rdx
  400cda:       48 03 42 f8             add    -0x8(%rdx),%rax
  400cde:       48 01 c1                add    %rax,%rcx
  400ce1:       48 81 fa 80 24 70 00    cmp    $0x702480,%rdx
  400ce8:       75 e6                   jne    400cd0 <_ZL7consumePv+0x20>
  400cea:       83 ee 01                sub    $0x1,%esi
  400ced:       75 d9                   jne    400cc8 <_ZL7consumePv+0x18>

生产者线程,带有空循环(不写入shared):

  400c90:       c7 05 e6 16 20 00 00    movl   $0x0,0x2016e6(%rip)        # 602380 <unshared>
  400c97:       00 00 00 
  400c9a:       0f b6 05 5f 16 20 00    movzbl 0x20165f(%rip),%eax        # 602300 <stop_producer>
  400ca1:       84 c0                   test   %al,%al
  400ca3:       74 eb                   je     400c90 <_ZL7producePv>

生产者线程,写入shared:

  400c90:       c7 05 66 17 20 00 00    movl   $0x0,0x201766(%rip)        # 602400 <shared>
  400c97:       00 00 00 
  400c9a:       0f b6 05 5f 16 20 00    movzbl 0x20165f(%rip),%eax        # 602300 <stop_producer>
  400ca1:       84 c0                   test   %al,%al
  400ca3:       74 eb                   je     400c90 <_ZL7producePv>

该程序计算消费者核心上消耗的 CPU 周期数,以完成整个循环。我们将第一个生产者(除了消耗 CPU 周期之外什么都不做)与第二个生产者(通过重复写入来扰乱消费者)进行比较。shared.

我的系统有 i5-4210U。即2个核心,每个核心2个线程。它们被内核公开为Core#1 → cpu0, cpu2 Core#2 → cpu1, cpu3.

根本没有启动生产者的结果:

CONSUMER    PRODUCER     cycles for 1M      cycles for 128k
    3          n/a           2.11G              1.80G

生产者为空的结果。对于 1G 操作(1000*1M 或 8000*128k)。

CONSUMER    PRODUCER     cycles for 1M      cycles for 128k
    3           3            3.20G              3.26G       # mono
    3           2            2.10G              1.80G       # other core
    3           1            4.18G              3.24G       # same core, HT

正如预期的那样,由于两个线程都占用了 cpu 并且都获得了公平的份额,因此生产者销毁周期使消费者的速度减慢了大约一半。这只是 cpu 争用。

对于生产者在 cpu#2 上,由于没有交互,消费者的运行不会受到在另一个 cpu 上运行的生产者的影响。

通过 cpu#1 上的生产者,我们可以看到超线程正在发挥作用。

颠覆性生产者的结果:

CONSUMER    PRODUCER     cycles for 1M      cycles for 128k
    3           3            4.26G              3.24G       # mono
    3           2           22.1 G             19.2 G       # other core
    3           1           36.9 G             37.1 G       # same core, HT
  • 当我们将两个线程调度到同一核心的同一线程上时,不会产生任何影响。再次预期,因为生产者写入保持在本地,不会产生同步成本。

  • 我无法真正解释为什么超线程的性能比两个核心的性能差得多。欢迎咨询。

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

生产者-消费者在超级兄弟与非超级兄弟之间共享内存位置的延迟和吞吐量成本是多少? 的相关文章

  • Mysql:多个表还是一张大表?

    这个问题已经被问过 但我还没有找到 1 个语音答案 最好这样做 1 张大桌子 其中 用户 ID 属性 1 属性 2 属性 3 属性 4 或 4 个小桌子 其中 用户 ID 属性 1 用户 ID 属性 2 用户 ID 属性 3 用户 ID 属
  • 应用程序中 GC 长时间暂停

    我当前运行的应用程序需要最大堆大小为 16GB 目前我使用以下标志来处理垃圾收集 XX UseParNewGC XX UseConcMarkSweepGC XX CMSInitiatingOccupancyFraction 50 XX Di
  • 如何反汇编、修改然后重新组装 Linux 可执行文件?

    无论如何 这可以做到吗 我使用过 objdump 但它不会产生我所知道的任何汇编器都可以接受的汇编输出 我希望能够更改可执行文件中的指令 然后对其进行测试 我认为没有任何可靠的方法可以做到这一点 机器代码格式非常复杂 比汇编文件还要复杂 实
  • C++,最佳实践,int 还是 size_t? [复制]

    这个问题在这里已经有答案了 可能的重复 何时使用 std size t https stackoverflow com questions 1951519 when to use stdsize t hello 假设使用模式相同 即没有负数
  • 汇编语言程序中连续两次相乘

    我正在使用 8086 模拟器以及 DOSBOX 和 MASM 我知道当我们将 8 位与 8 位相乘时 答案将是 16 位 al 8 bit ax 当我们将 16 位与 16 位相乘时 答案将是 32 位 ax 16 bit dx ax 但如
  • JS中函数声明速度差异

    我运行了一个简单的 jsperf 测试 在 Firefox 中运行时一切都按预期进行 但当我在 Google Chrome 中运行测试时却感到困惑 该测试正在测试在 JavaScript 中声明函数然后调用它们的不同方式 我的猜测是 Chr
  • 读取大文件并制作字典

    我有一个大文件 我需要读取它并从中制作字典 我希望这一切能够尽可能快 然而我的Python代码太慢了 这是一个显示问题的最小示例 首先制作一些假数据 paste lt seq 20000000 lt seq 2 20000001 gt la
  • 使用unittest时如何知道每次测试花费的时间?

    Unittest 仅显示运行所有测试所花费的总时间 但不单独显示每个测试所花费的时间 使用unittest时如何添加每个测试的计时 我想 目前不可能 http bugs python org issue4080 http bugs pyth
  • 将 numpy 数组写入文本文件的速度

    我需要将一个非常 高 的两列数组写入文本文件 而且速度非常慢 我发现如果我将数组改造成更宽的数组 写入速度会快得多 例如 import time import numpy as np dataMat1 np random rand 1000
  • 获取 Future 对象的进度的能力

    参考 java util concurrent 包和 Future 接口 我注意到 除非我弄错了 只有 SwingWorker 实现类才能启动冗长的任务并能够查询进度 这就引出了以下问题 有没有办法在非 GUI 非 Swing 应用程序 映
  • 在Linux中修改子进程的全局变量

    我有 C 背景 在 C 并发方面遇到了一些困难 我不会对你撒谎 这是我必须为学校做的一个项目的一部分 虽然在专业上我曾使用过高级语言 但我的高级论文教授强迫全班同学使用 C 语言编写代码 我们大多数人几乎没有这方面的经验 从而让我们陷入了困
  • Java 反射性能

    使用反射创建对象而不是调用类构造函数是否会导致任何显着的性能差异 是的 一点没错 通过反射查找类是 按幅度 更贵 Quoting Java关于反射的文档 http java sun com docs books tutorial refle
  • CSS3 - 性能最佳实践是什么? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何在 AVX/AVX2 中递增向量

    我想使用内在函数来增加 SIMD 向量的元素 最简单的方法似乎是为每个元素加 1 如下所示 note vec inc之前已设置为1 vec mm256 add epi16 vec vec inc 但是是否有任何特殊指令来增加向量 类似于in
  • Numpy 与 Cython 速度

    我有一个分析代码 它使用 numpy 执行一些繁重的数值运算 只是出于好奇 尝试使用 cython 进行少量更改来编译它 然后我使用 numpy 部分的循环重写它 令我惊讶的是 基于循环的代码要快得多 8 倍 我无法发布完整的代码 但我整理
  • C++ OpenCV imdecode 慢

    我将图像的字节数组从 C 发送到 C 库 我使用 OpenCV 版本 3 3 1 解码图像 BMP 图像解码速度很快 但 JPEG 图像解码速度很慢 如何加快 JPEG 图像的解码时间 多线程 GPU 解码性能 Resolution For
  • 由于内容不可压缩,谷歌浏览器中出现了新的复合层

    当 chrome profiler 说 图层是单独合成的 因为它无法被挤压 时 它到底意味着什么 我正在对我的 html 进行更改 并在相对 div 内引入了一个固定位置 div 并给出了will change transform在上面 完
  • .NET 或 Windows 同步原语性能规范

    我目前正在写一篇科学文章 我需要非常准确地引用 有人可以向我指出 MSDN MSDN 文章 一些已发表的文章来源或一本书 我可以在其中找到 Windows 或 NET 同步原语的性能比较 我知道这些是按性能降序排列的 互锁 API 关键部分
  • 与简单的文件请求相比,您预计 Web 服务请求的响应时间开销是多少?

    我正在开发一个 asp net Web 服务应用程序 以向使用 jQuery ajax 发出请求的小部件提供 json 格式的数据 我一直在使用 FireBug Net 视图来检查数据请求需要多长时间 在我最初的原型中 我只是请求静态 js
  • ListDictionary 类是否有通用替代方案?

    我正在查看一些示例代码 其中他们使用了ListDictionary对象来存储少量数据 大约 5 10 个对象左右 但这个数字可能会随着时间的推移而改变 我使用此类的唯一问题是 与我所做的其他所有事情不同 它不是通用的 这意味着 如果我在这里

随机推荐