【问题标题】:In C++, which one is faster? taking input, storing in array and adding simultaneously or taking input and storing as array and later summing up?在 C++ 中,哪个更快?获取输入,存储在数组中并同时添加或获取输入并存储为数组,然后汇总?
【发布时间】:2021-09-11 05:31:10
【问题描述】:

我有一个场景,我必须从用户那里获取 n 个输入并将其存储在一个数组中,我还需要找到数组中所有元素的总和。 由于它在 codeforces 中,所以我想知道最优化的方法。

通常,I/O 操作因占用大量 CPU 时间而臭名昭著。如果我经常从 I/O 和处理切换而不是先获取普通输入然后再处理总和,它是否会对大规模输入(即 1000 次输入)造成性能影响?

我正在使用 C++ 语言和在线法官(Codeforces)。

第一种方法:我循环 n 次并从输入流中读取 n 个输入,并将它们一个接一个地放入数组中。然后把它们全部加起来。

int n, sum=0;
cin >> n;

int *myarray = new int[n];

for(int i=0; i<n; i++)
{
    cin >> myarray[i];
}

for(int i=0; i<n; i++)
{
    sum += myarray[i];
}

第二种方法:我循环n次并读取n个输入。每次,我将它放在一个数组中,并将我刚刚作为输入的数字相加。

int n, sum=0;
cin >> n;

int *myarray = new int[n];

for(int i=0; i<n; i++)
{
    cin >> myarray[i];
    sum += myarray[i];
}

【问题讨论】:

  • 对输入求和,不需要数组
  • 您能做的最好的事情就是自己测试一下。运行第一个代码 X 次,计算结果。然后运行第二个代码 X 次,计算结果。哪个更快?
  • "I/O 操作因占用大量 CPU 时间而臭名昭著": false。它们花费 已用 时间,而 CPU 时间很少或根本没有。当您从cin 阅读时,您受到用户输入速度的 I/O 限制,这比 CPU 慢了许多数量级,这太荒谬了。没有理由期望您的两种方案之间有任何区别。

标签: c++ arrays performance input output


【解决方案1】:

在这样的程序中,您有一个单独的线程来接收用户输入,将其存储在一个数组中并求和,不会有(明显的)差异 - 无论如何都会发生相同的操作。

更明显的优化是丢失数组。如果所需的输出只是总和,您可以直接对输入求和而不存储它们:

int sum = 0;
for(int i=0; i<n; i++)
{
    int temp;
    cin >> temp;
    sum += temp;
}

【讨论】:

    【解决方案2】:

    我怀疑在实践中,与您的函数中的其他操作相比,cin 是如此缓慢,以至于任何差异都不太可能是可测量的。

    也就是说,如果我们忽略 cin 的缓慢性并专注于您的函数生成的代码的质量,我会看到单循环方法的几个优点。

    • 第一个示例包含所有循环控制指令两次,这些指令便宜但不是免费的。
    • 最近写入的内存位置几乎可以肯定仍然在 L1 缓存中,如果迭代次数很大,则在前一个循环中写入的内存位置可能不在。

    在一个循环中做太多不同的事情的一个可能的缺点是您可能会用完“被调用者保存”寄存器,从而迫使代码将堆栈用于原本可以存在于寄存器中的变量,但我没有认为这将是大多数架构的问题。

    在查看 Godbolt 上的编译代码时,我注意到的另一件事是,由于 cin 接受引用而不是返回值,因此“n”不能跨函数调用存在于寄存器中。所以调用 cin 的循环必须在每次迭代时重新加载它。

    【讨论】:

      【解决方案3】:

      严格来说,在一个循环中完成所有操作会更快,但您不必担心差异。

      对于 Codeforces,你可能想要更快的 io,所以我建议你使用 scanfprintf 而不是 cincout,它们是 known 更快。

      如果你想使用cincout,你也可以添加这行,这样会更快,你可以检查为什么here

      ios_base::sync_with_stdio(false);
      cin.tie(NULL);
      

      为什么?
      当这段代码翻译成汇编时,循环只是一个条件跳转语句,根据条件,跳转可能采取不采取,只有当条件被评估时,我们才能知道。
      我们肯定不想等待条件评估,所以我们有branch prediction(它可以通过多种方式完成),它会执行“一些计算”来估计分支是否被采用或不。
      在评估条件之前,我们遵循我们的假设并执行指令,而不会将它们的结果提交到寄存器而不是内存。

      如果我们的估计正确,则提交这些结果(我们自然会继续执行),否则我们只需刷新管道(丢弃这些指令及其结果)。

      这种未命中预测损失(我们不需要计算的结果)仍然会降低我们的性能,这就是减少对流指令的控制有助于提高性能的原因。

      对于您的情况,将循环分成两个,将产生两个条件跳转指令而不是一个,这会增加未命中惩罚。

      一些编译器使用loop unrolling 来解决与此类似的问题。

      只要您不使用 HPC(高性能计算)应用程序,或者确实需要不惜一切代价进行更好的优化,您就不必担心这一点。

      而是专注于改进算法本身,正如@Mureinik 所说,如果您的问题只需要求和,而不进一步使用数字本身,您可以即时计算总和。

      【讨论】:

        猜你喜欢
        • 2014-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-07-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多