为什么 std::fill(0) 比 std::fill(1) 慢?

2024-01-09

我观察到一个系统std::fill在一个大的std::vector<int>设置恒定值时明显且持续变慢0与恒定值相比1或动态值:

5.8 GiB/秒 vs 7.5 GiB/秒

然而,对于较小的数据量,结果是不同的,其中fill(0)是比较快的:

具有多个线程,数据大小为 4 GiB,fill(1)显示出更高的斜率,但达到的峰值比fill(0)(51 GiB/秒与 90 GiB/秒):

这就提出了第二个问题,为什么峰值带宽fill(1)低得多。

测试系统是双路 Intel Xeon CPU E5-2680 v3,频率设置为 2.5 GHz(通过/sys/cpufreq)配备 8x16 GiB DDR4-2133。我用 GCC 6.1.0 进行了测试(-O3)和英特尔编译器 17.0.1(-fast),两者得到相同的结果。GOMP_CPU_AFFINITY=0,12,1,13,2,14,3,15,4,16,5,17,6,18,7,19,8,20,9,21,10,22,11,23已设置。 Strem/add/24 线程在系统上获得 85 GiB/s。

我能够在不同的 Haswell 双插槽服务器系统上重现这种效果,但不能在任何其他架构上重现。例如,在 Sandy Bridge EP 上,内存性能相同,而在缓存中fill(0)速度要快得多。

这是重现的代码:

#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <omp.h>
#include <vector>

using value = int;
using vector = std::vector<value>;

constexpr size_t write_size = 8ll * 1024 * 1024 * 1024;
constexpr size_t max_data_size = 4ll * 1024 * 1024 * 1024;

void __attribute__((noinline)) fill0(vector& v) {
    std::fill(v.begin(), v.end(), 0);
}

void __attribute__((noinline)) fill1(vector& v) {
    std::fill(v.begin(), v.end(), 1);
}

void bench(size_t data_size, int nthreads) {
#pragma omp parallel num_threads(nthreads)
    {
        vector v(data_size / (sizeof(value) * nthreads));
        auto repeat = write_size / data_size;
#pragma omp barrier
        auto t0 = omp_get_wtime();
        for (auto r = 0; r < repeat; r++)
            fill0(v);
#pragma omp barrier
        auto t1 = omp_get_wtime();
        for (auto r = 0; r < repeat; r++)
            fill1(v);
#pragma omp barrier
        auto t2 = omp_get_wtime();
#pragma omp master
        std::cout << data_size << ", " << nthreads << ", " << write_size / (t1 - t0) << ", "
                  << write_size / (t2 - t1) << "\n";
    }
}

int main(int argc, const char* argv[]) {
    std::cout << "size,nthreads,fill0,fill1\n";
    for (size_t bytes = 1024; bytes <= max_data_size; bytes *= 2) {
        bench(bytes, 1);
    }
    for (size_t bytes = 1024; bytes <= max_data_size; bytes *= 2) {
        bench(bytes, omp_get_max_threads());
    }
    for (int nthreads = 1; nthreads <= omp_get_max_threads(); nthreads++) {
        bench(max_data_size, nthreads);
    }
}

所呈现的结果编译g++ fillbench.cpp -O3 -o fillbench_gcc -fopenmp.


从你的问题+编译器生成的汇编从你的答案:

  • fill(0) is an ERMSB rep stosb https://stackoverflow.com/questions/43343231/enhanced-rep-movsb-for-memcpy它将在优化的微编码循环中使用 256b 存储。 (如果缓冲区对齐,效果最好,可能至少为 32B 或 64B)。
  • fill(1)是一个简单的128位movaps矢量存储循环。无论宽度如何,每个核心时钟周期只能执行一个存储,最多 256b AVX。所以128b存储只能填满Haswell L1D缓存写入带宽的一半。这就是为什么fill(0)对于高达约 32kiB 的缓冲区,速度约为 2 倍。编译用-march=haswell or -march=native解决这个问题.

    Haswell 只能勉强跟上循环开销,但它仍然可以每个时钟运行 1 个存储,即使它根本没有展开。但每个时钟有 4 个融合域微指令,大量填充会占用乱序窗口中的空间。一些展开可能会让 TLB 未命中在存储发生之前更早地开始解决,因为存储地址微指令的吞吐量比存储数据的吞吐量更大。展开可能有助于弥补 ERMSB 和适合 L1D 的缓冲区的向量循环之间的其余差异。 (对该问题的评论说-march=native只帮助了fill(1)对于 L1。)

注意rep movsd(可以用来实现fill(1) for int元素)可能会执行相同的操作rep stosb在哈斯韦尔上。 虽然只有官方文档只能保证 ERMSB 给出快速rep stosb(但不是rep stosd), 支持 ERMSB 的实际 CPU 使用类似高效的微代码rep stosd https://stackoverflow.com/questions/42558907/why-is-stdfill0-slower-than-stdfill1/45018779?noredirect=1#comment77014359_45018779。对IvyBridge有一些疑问,可能只是b速度很快。看看@BeeOnRope 的精彩ERMSB 答案 https://stackoverflow.com/questions/43343231/enhanced-rep-movsb-for-memcpy有关这方面的更新。

gcc 有一些用于字符串操作的 x86 调整选项(like -mstringop-strategy=alg and -mmemset-strategy=strategy https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html),但我不知道他们中的任何一个是否能够让它真正发出rep movsd for fill(1)。可能不是,因为我假设代码是作为循环开始的,而不是memset.


对于多个线程,在 4 GiB 数据大小下,fill(1) 显示出更高的斜率,但达到的峰值比 fill(0) 低得多(51 GiB/s 与 90 GiB/s):

一个正常的movaps存储到冷缓存行会触发阅读所有权 (RFO) https://en.wikipedia.org/wiki/MESI_protocol#Read_For_Ownership。大量实际 DRAM 带宽花费在从内存读取缓存行上movaps写入前 16 个字节。 ERMSB 存储对其存储使用无 RFO 协议,因此内存控制器仅进行写入。 (除了杂项读取之外,例如页表(即使在 L3 缓存中也有任何页遍历丢失),也可能是中断处理程序中的某些加载丢失或其他)。

@BeeOnRope在评论中解释 https://stackoverflow.com/questions/42558907/why-is-stdfill0-slower-than-stdfill1/45018779?noredirect=1#comment77014609_45018779常规 RFO 存储与 ERMSB 使用的 RFO 避免协议之间的差异对于服务器 CPU 上的某些缓冲区大小范围有缺点,其中非核心/L3 缓存中存在高延迟。另请参阅链接的 ERMSB 答案,了解有关 RFO 与非 RFO 的更多信息,以及多核 Intel CPU 中非核心(L3/内存)的高延迟是单核带宽的问题。


movntps (_mm_stream_ps()) stores是弱排序的,因此它们可以绕过缓存并一次直接将整个缓存行读取到内存,而无需将缓存行读入 L1D。movntps避免 RFO,例如rep stos做。 (rep stos存储可以相互重新排序,但不能超出指令的范围。)

Your movntps您更新的答案的结果令人惊讶。
对于具有大缓冲区的单个线程,您的结果是movnt>> 常规 RFO > ERMSB。所以这真的很奇怪,两种非 RFO 方法位于普通旧存储的两侧,而且 ERMSB 远非最佳。目前我对此还没有任何解释。 (欢迎编辑并提供解释+良好的证据)。

正如我们预期的那样,movnt允许多个线程实现高聚合存储带宽,例如 ERMSB。movnt总是直接进入行填充缓冲区,然后进入内存,因此适合缓存的缓冲区大小要慢得多。每个时钟一个 128b 矢量足以轻松饱和单核到 DRAM 的无 RFO 带宽。大概vmovntps ymm(256b)仅比vmovntps xmm(128b) 当存储 CPU 绑定的 AVX 256b 向量化计算的结果时(即仅当它省去了解包到 128b 的麻烦时)。

movnti带宽较低,因为存储在 4B 块中的瓶颈在于每个时钟 1 个存储 uop 将数据添加到行填充缓冲区,而不是将这些行满缓冲区发送到 DRAM(直到您有足够的线程使内存带宽饱和)。


@osgx 发布评论中一些有趣的链接 https://stackoverflow.com/questions/42558907/why-is-stdfill0-slower-than-stdfill1#comment72972067_42558908:

  • Agner Fog 的 asm 优化指南、指令表和微架构指南:http://agner.org/optimize/ http://agner.org/optimize/
  • 英特尔优化指南:http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf.

  • NUMA 窥探:http://frankdenneman.nl/2016/07/11/numa-deep-dive-part-3-cache-coherency/ http://frankdenneman.nl/2016/07/11/numa-deep-dive-part-3-cache-coherency/

  • https://software.intel.com/en-us/articles/intelr-memory-latency-checker https://software.intel.com/en-us/articles/intelr-memory-latency-checker
  • 缓存一致性协议和内存 英特尔 Haswell-EP 架构的性能 https://tu-dresden.de/zih/forschung/ressourcen/dateien/abgeschlossene-projekte/benchit/2015_ICPP_authors_version.pdf?lang=en

另请参阅中的其他内容x86 /questions/tagged/x86标签维基。

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

为什么 std::fill(0) 比 std::fill(1) 慢? 的相关文章

  • 添加对共享类的多个 WCF 服务的服务引用

    我正在尝试将我的 WCF Web 服务拆分为几个服务 而不是一个巨大的服务 但是 Visual Studio Silverlight 客户端 复制了两个服务共享的公共类 这是一个简单的例子来说明我的问题 在此示例中 有两个服务 两者都返回类
  • 如果.Net Core可以在Windows上运行,为什么不能在.Net Framework中引用.Net Core DLL?

    我明白为什么 Net Framework 可能会在 Net Core IE 中导致问题 因为不存在特定于 Windows 平台的 API 但是为什么不能直接引用 Net Core 作为 Net Framework 中的库呢 如果 Net C
  • 在 OpenCL 中将函数作为参数传递

    是否可以在 OpenCL 1 2 中将函数指针传递给内核 我知道可以用C实现 但不知道如何在OpenCL的C中实现 编辑 我想做这篇文章中描述的同样的事情 在 C 中如何将函数作为参数传递 https stackoverflow com q
  • 处理 fanart.tv Web 服务响应 JSON 和 C#

    我正在尝试使用 fanart tv Webservice API 但有几个问题 我正在使用 Json Net Newtonsoft Json 并通过其他 Web 服务将 JSON 响应直接反序列化为 C 对象 这里的问题是元素名称正在更改
  • try-catch 中未处理的异常

    try list from XElement e in d Descendants wix File where e Attribute Name Value Contains temp Name e Parent Parent Attri
  • 为什么 BOOST_FOREACH 不完全等同于手工编码的?

    From 增强文档 http www boost org doc libs 1 48 0 doc html foreach html foreach introduction what is literal boost foreach li
  • 如何用 kevent() 替换 select() 以获得更高的性能?

    来自Kqueue 维基百科页面 http en wikipedia org wiki Kqueue Kqueue 在内核和用户空间之间提供高效的输入和输出事件管道 因此 可以修改事件过滤器以及接收待处理事件 同时每次主事件循环迭代仅使用对
  • Xamarin Android:获取内存中的所有进程

    有没有办法读取所有进程 而不仅仅是正在运行的进程 如果我对 Android 的理解正确的话 一次只有一个进程在运行 其他所有进程都被冻结 后台进程被忽略 您可以使用以下代码片段获取当前正在运行的所有 Android 应用程序进程 Activ
  • 单元测试失败,异常代码为 c0000005

    我正在尝试使用本机单元测试项目在 Visual Studios 2012 中创建单元测试 这是我的测试 TEST METHOD CalculationsRoundTests int result Calculations Round 1 0
  • 为什么 FTPWebRequest 或 WebRequest 通常不接受 /../ 路径?

    我正在尝试从 ftp Web 服务器自动执行一些上传 下载任务 当我通过客户端甚至通过 Firefox 连接到服务器时 为了访问我的目录 我必须指定如下路径 ftp ftpserver com AB00000 incoming files
  • 范围和临时初始化列表

    我试图将我认为是纯右值的内容传递到范围适配器闭包对象中 除非我将名称绑定到初始值设定项列表并使其成为左值 否则它不会编译 这里发生了什么 include
  • 两组点之间的最佳匹配

    I ve got two lists of points let s call them L1 P1 x1 y1 Pn xn yn and L2 P 1 x 1 y 1 P n x n y n 我的任务是找到它们点之间的最佳匹配 以最小化它
  • 通过等待任务或访问其 Exception 属性都没有观察到任务的异常

    这些是我的任务 我应该如何修改它们以防止出现此错误 我检查了其他类似的线程 但我正在使用等待并继续 那么这个错误是怎么发生的呢 通过等待任务或访问其 Exception 属性都没有观察到任务的异常 结果 未观察到的异常被终结器线程重新抛出
  • 过期时自动重新填充缓存

    我当前缓存方法调用的结果 缓存代码遵循标准模式 如果存在 则使用缓存中的项目 否则计算结果 在返回之前将其缓存以供将来调用 我想保护客户端代码免受缓存未命中的影响 例如 当项目过期时 我正在考虑生成一个线程来等待缓存对象的生命周期 然后运行
  • 哪些属性有助于运行时 .Net 性能?

    我正在寻找可用于通过向加载器 JIT 编译器或 ngen 提供提示来确保 Net 应用程序获得最佳运行时性能的属性 例如我们有可调试属性 http msdn microsoft com en us library k2wxda47 aspx
  • 为什么 Ajax.BeginForm 在 Chrome 中不起作用?

    我正在使用 c NET MVC2 并尝试创建一个 ajax 表单来调用删除数据库记录 RemoveRelation 的方法 删除记录的过程正在按预期进行 删除记录后 表单应调用一个 JavaScript 函数 从视觉效果中删除该记录 Rem
  • 在基类集合上调用派生方法

    我有一个名为 A 的抽象类 以及实现 A 的其他类 B C D E 我的派生类持有不同类型的值 我还有一个 A 对象的列表 abstract class A class B class A public int val get privat
  • Azure函数版本2.0-应用程序blobTrigger不工作

    我有一个工作功能应用程序 它有一个 blob 输入和一个事件中心输出 在测试版中工作 随着最新的更改 我的功能不再起作用 我尝试根据发行说明更新 host json 文件 但它没有引用 blob 触发器 version 2 0 extens
  • WPF/数据集:如何通过 XAML 将相关表中的数据绑定到数据网格列中?

    我正在使用 WPF DataSet 连接到 SQL Server Express XAML 和 C Visual Studio 2013 Express 我从名为 BankNoteBook 的现有 SQL Server Express 数据
  • 文件修改时间检查的成本

    对于Linux下包含少量字节的文件 我只需要处理自上次处理以来发生更改的时间 我通过调用 PHP 检查文件是否被更改clearstatcache filemtime 定期 由于整个文件总是很小 因此删除对 filemtime 的调用并通过将

随机推荐

  • 在sql server中将列转换为行及其各自的数据

    我有一个场景 我需要将表的列转换为行 例如 表 库存 ScripName ScripCode Price 20 MICRONS 533022 39 我需要用以下格式表示表格 但我只需要这种单行表示 ColName ColValue Scri
  • 寻找 RSS 应用程序的 Google Reader 同步替代方案

    我正处于设计 RSS 应用程序的早期阶段 我希望将同步到在线 RSS 提要服务作为一项功能 大多数此类应用程序都利用 Google Reader 的提要 同步功能 但 Google 现在正在将同步功能从 Reader 服务中移出 而且其 A
  • React:访问React内部操作队列

    React 收集操作 https reactjs org docs implementation notes html updating host components 就像 ADD REPLACE REMOVE 等 DOM 操作一样 这样
  • 如何使用最新的 Facebook sdk 从 iOS 中的 Facebook API 获取用户的生日?

    如何使用最新的 Facebook sdk 从 iOS 中的 Facebook API 获取用户的生日 我尝试去获取它 fields id name link first name last name picture type large e
  • 如何从 Java 代码更新 Jenkins config.xml?

    我是 Jenkins 插件开发的新手 所以如果问题很愚蠢 请原谅我 我目前正在开发一个 Jenkins 插件 它提供了一个非常小的配置选项列表 如所附屏幕截图所示 该表单是使用 Jelly 脚本设计的 我必须从我的 Java 代码更新作业的
  • Tensorflow 中的 zip 之类的函数? Tensorflow张量运算

    我的问题是关于 Tensorflow 中的张量运算 比方说 import tensorflow as tf import numpy as np a tf Variable np random random 10 3 3 b tf Vari
  • Javascript d3:有没有办法以编程方式停止拖动项目?

    当我单击并拖动项目时 有没有办法在不松开鼠标按钮的情况下强制使其停止可拖动 例如 如果我将一个项目拖过某个边界框 我可以让它放开所拖动的项目吗 jsfiddle 示例 http jsfiddle net typeofgraphic Ne8h
  • 在 Sublime Text 中编写查找和替换操作脚本

    我经常发现自己在文件中执行重复的文件和替换操作 最常见的是fixed查找和替换操作 删除一些行 更改一些始终相同的字符串等等 在 Vim 中这是理所当然的 function Modify Strength Files execute s e
  • 使用 ProGuard 和 Firebase Auth 进行 Flutter 构建崩溃

    我跟着有关将 ProGuard 添加到 Flutter 的说明 https flutter io android release step 1 configure proguard现在在启动应用程序时看到此异常 java lang NoCl
  • 硒下载文件

    我正在尝试制作一个 Selenium 程序来自动下载和上传一些文件 请注意 我这样做不是为了测试 而是为了尝试自动化某些任务 这是我对 Firefox 配置文件的 set preference profile set preference
  • ASP.NET MVC 2.0 JsonRequestBehavior 全局设置

    默认情况下 当操作尝试返回 JSON 以响应 GET 请求时 ASP NET MVC 2 0 现在将引发异常 我知道这可以通过使用 JsonRequestBehavior AllowGet 逐个方法地覆盖 但是是否可以在控制器或更高的基础上
  • .htaccess 将 https 重定向到 http 不起作用

    我正在尝试捕获到我的网站前面的任何 https 流量 因此 https www domain com 被重定向到 http www domain com 然而 其他子域需要重定向到其他地方 在大多数情况下 除了 https gt http
  • 域名指向单个页面

    我试图将域名指向单个页面 并保持域名相同 无重定向 因此 如果用户输入 www domain1 com au gt 将显示原始站点 如果用户输入 www domain2 com au gt 他们会显示 www domain1 com au
  • ubuntu:sem_timedwait 未唤醒 (C)

    我有3个进程需要同步 进程一执行某项操作 然后唤醒进程二并休眠 进程二执行某项操作 然后唤醒进程三并休眠 进程三执行某项操作 唤醒进程一并休眠 整个循环定时运行在 25hz 左右 由于在我的 真实 应用程序中触发进程二之前 外部同步到进程一
  • 使用CodeIgniter从mysql数据库中随机记录

    我在互联网上进行了研究 但找不到任何东西 我有一个 mysql 数据库 并在一个表中记录 我需要在每次页面加载时从该表中获取随机记录 我怎样才能做到这一点 有什么功能吗 欣赏 谢谢 已排序 关联 http www derekallard c
  • 在 Python 或 C++ 中单声道播放 Mp3

    我正在编码音乐播放器 https github com fabiomdiniz Gokya 2 The Super Gokya 在 python 中使用 pyqt 我希望它能够单声道播放 mp3 文件 我已经使用 pygame 完成了这一点
  • SQL 中的日期范围交集分割

    我有一个 SQL Server 2005 数据库 其中包含一个名为 成员资格 的表 表架构为 PersonID int Surname nvarchar 30 FirstName nvarchar 30 Description nvarch
  • 强制自动启动计算机?

    我们知道如何使用 Java 强制关闭计算机 例如 以下代码可以很好地强制关闭 public static void main String arg throws IOException Runtime runtime Runtime get
  • 为什么溢出:隐藏会为内联块元素添加额外的高度?

    In 这个例子 http jsbin com zowejokuluce 4 HTML div div foo bar div div CSS body html div height 100 margin 0 div div display
  • 为什么 std::fill(0) 比 std::fill(1) 慢?

    我观察到一个系统std fill在一个大的std vector