【问题标题】:Why is the printing of array elements in average slower than the printing of single objects in C++?为什么数组元素的打印平均比 C++ 中单个对象的打印慢?
【发布时间】:2020-01-23 17:16:21
【问题描述】:

我做了一个测试,通过将它们的值打印到 CLI 中来查看访问数组元素和单个对象之间的区别:

#include <iostream>
#include <chrono>
#include <iomanip>

int main()
{
    int a[10] = {1,2,3,4,5,6,7,8,9,10};

    int v1 = 1;
    int v2 = 2;
    int v3 = 3;
    int v4 = 4;
    int v5 = 5;
    int v6 = 6;
    int v7 = 7;
    int v8 = 8;
    int v9 = 9;
    int v10 = 10;

    std::cout << "Array output:" << std::endl << std::endl;

    auto t_start1 = std::chrono::high_resolution_clock::now();

    std::cout << "1. value: " << a[0] << std::endl;
    std::cout << "2. value: " << a[1] << std::endl;
    std::cout << "3. value: " << a[2] << std::endl;
    std::cout << "4. value: " << a[3] << std::endl;
    std::cout << "5. value: " << a[4] << std::endl;
    std::cout << "6. value: " << a[5] << std::endl;
    std::cout << "7. value: " << a[6] << std::endl;
    std::cout << "8. value: " << a[7] << std::endl;
    std::cout << "9. value: " << a[8] << std::endl;
    std::cout << "10. value: " << a[9] << std::endl;

    auto t_end1 = std::chrono::high_resolution_clock::now();

    std::cout << std::endl;

    std::cout << "Variable output:" << std::endl << std::endl;

    auto t_start2 = std::chrono::high_resolution_clock::now();

    std::cout << "1. value: " << v1 << std::endl;
    std::cout << "2. value: " << v2 << std::endl;
    std::cout << "3. value: " << v3 << std::endl;
    std::cout << "4. value: " << v4 << std::endl;
    std::cout << "5. value: " << v5 << std::endl;
    std::cout << "6. value: " << v6 << std::endl;
    std::cout << "7. value: " << v7 << std::endl;
    std::cout << "8. value: " << v8 << std::endl;
    std::cout << "9. value: " << v9 << std::endl;
    std::cout << "10. value: " << v10 << std::endl;


    auto t_end2 = std::chrono::high_resolution_clock::now();

    std::cout<< std::endl << "Time passed with array: "
              << std::chrono::duration<double, std::milli>(t_end1-t_start1).count()
              << " ms\n" << std::endl;
    std::cout<< std::endl << "Time passed with variables: "
              << std::chrono::duration<double, std::milli>(t_end2-t_start2).count()
              << " ms\n" << std::endl;

    return 0;
}

在第一个实现(Windows 10 下的 MingW/g++,cmd.exe)中,数组元素内的值的打印平均比使用单个标量对象慢 3 毫秒

Windows 表,g++/MingW:

                Array Elements:            Single Objects:   

1. Run          13.9609 ms                 9.529 ms
2. Run          11.9031 ms                 8.0936 ms
3. Run          13.3706 ms                 9.5264 ms
4. Run          12.5302 ms                 8.4723 ms
5. Run          14.4679 ms                 9.9688 ms
6. Run          12.3989 ms                 8.4326 ms
7. Run          12.8719 ms                 10.1851 ms
8. Run          10.9138 ms                 7.4481 ms
9. Run          12.8971 ms                 9.4094 ms
10. Run         11.9045 ms                 7.9391 ms
11. Run          9.9192 ms                 8.4047 ms
12. Run         13.4106 ms                 10.0296 ms

在第二个实现中(Linux Ubuntu 下的 g++),数组元素内的值的打印平均比使用单个标量对象慢 3 微秒

适用于 Linux Ubuntu、g++ 的表:

                Array Elements:            Single Objects:   

1. Run          0.013 ms                   0.008 ms
2. Run          0.012 ms                   0.007 ms
3. Run          0.013 ms                   0.008 ms
4. Run          0.014 ms                   0.009 ms
5. Run          0.012 ms                   0.008 ms
6. Run          0.013 ms                   0.008 ms
7. Run          0.013 ms                   0.009 ms
8. Run          0.014 ms                   0.009 ms
9. Run          0.012 ms                   0.008 ms
10. Run         0.013 ms                   0.009 ms
11. Run         0.012 ms                   0.009 ms
12. Run         0.012 ms                   0.008 ms 

我的问题:

  • 为什么在 *C++ 中打印数组元素中的值平均比打印单个对象中的值慢?

*信息:我不知道数组元素的打印通常是否平均较慢,与特定语言无关。

【问题讨论】:

  • 我很困惑为什么 Mingw 这么慢。
  • 从根本上说,访问对象是直接的。访问数组的元素需要计算元素的偏移量、位置或地址(从数组的开头开始)。许多现代处理器可以在一条指令中做到这一点。
  • 优化构建还是调试构建?这很重要。
  • @drescherjm 这是因为Windows系统与Linux相比。
  • 可能是打印到标准输出所需的时间在 windows 中比在 linux 中更糟

标签: c++ arrays performance benchmarking microbenchmark


【解决方案1】:

如果您查看generated assembly,您会注意到编译器将数组元素和标量的负载替换为常量,因为它们的值在编译时是已知的。


您测量格式化和输出数组元素所需的时间,以及刷新每个元素上的输出流(使用std::endl),这涉及系统调用。

从数组中加载元素比这要短得多。从一级缓存加载 CPU 寄存器需要 4 个 CPU 周期(在 5GHz CPU 上不到 1 纳秒),从内存(最后一级缓存未命中)在 i9-i9900KS ~215 个 CPU 周期,在 Ryzen ~280 个 CPU 周期。

从数组或(标量)变量加载元素之间应该没有可测量的差异。从数组加载元素可能会在生成的程序集中涉及一些索引算法,但这可能很难测量。


当我围绕您的时间设置一个循环以让 CPU 将其频率提高到最大时,页面错误页面会进入并预热 CPU 缓存;并将std::endl 替换为'\n' 我得到以下时间:

Time passed with array:     0.029154 ms
Time passed with variables: 0.029286 ms

Time passed with array:     0.027148 ms
Time passed with variables: 0.027587 ms

这是为了表明访问数组元素和标量变量之间没有可测量的时间差(但它仍在测量std::cout 次)。

【讨论】:

  • @MaximEgorushkin 好的,这给出了格式化和输出与访问之间区别的答案。但是为什么它平均打印出数组值比标量类型的值慢?
  • @ 可能是 CPU 进入更高的频率、页面错误、缓存预热。为了获得准确的测量,CPU 必须锁定在一个恒定的频率,基准测试应该做几个循环来预热缓存和页面错误页面。只有在测量时间之后。在答案中添加了时间。
  • @RobertS-ReinstateMonica:当您有令人惊讶的基准测试结果时,请尝试按其他顺序进行测试。因此,另一个必须在定时区域内支付启动成本,和/或在 CPU 尚未达到最大 turbo 的情况下运行。或者制作单独的程序(一个用于数组,一个用于标量)。这样,您可以看到“数组”的额外成本实际上是第一次调用std::cout.operator&lt;&lt;(int) 的额外成本,或者只是 CPU 频率尚未预热。当然,如果您足够了解这样做,您就会知道在计时区域之前等进行一些热身。
【解决方案2】:

在您的测试中,将内容写入 cout 比仅访问内存中的内容要花费更多时间。

更糟糕的是,它甚至可能不会从数组或变量中读取值,因为该值在编译时就已经知道了。它可能只是在这两种情况下都使用常量。

为了证明这一切,只需尝试与硬编码常量的输出时间进行比较。并尝试将其与计算和输出这些数字之和所需的时间进行比较。

但我们无法确定第一次输出速度较慢的原因。这仅取决于操作系统如何处理这些输出流。

【讨论】:

  • 恕我直言,更好的验证方法是打印汇编语言。 :-)
  • 您需要为此了解汇编,了解汇编的人往往会理解 stdout 比从 RAM 中读取花费的时间要长得多
猜你喜欢
  • 2014-03-23
  • 1970-01-01
  • 1970-01-01
  • 2022-01-13
  • 2012-09-25
  • 1970-01-01
  • 1970-01-01
  • 2017-02-09
相关资源
最近更新 更多