为什么 c++ std::max_element 这么慢?

2024-02-21

我需要找到向量中的最大元素,所以我使用std::max_element,但我发现它是一个非常慢的函数,所以我编写了自己的版本并设法获得 x3 更好的性能,下面是代码:

#include <string>
#include <iostream>
#include <vector>
#include <algorithm>

#include <sys/time.h>

double getRealTime()
{
    struct timeval tv;
    gettimeofday(&tv, 0);
    return (double) tv.tv_sec + 1.0e-6 * (double) tv.tv_usec;
}

inline int my_max_element(const std::vector<int> &vec, int size)
{
    auto it = vec.begin();
    int max = *it++;
    for (; it != vec.end(); it++)
    {
        if (*it > max)
        {
            max = *it;
        }
    }
    return max;
}

int main()
{
    const int size = 1 << 20;
    std::vector<int> vec;
    for (int i = 0; i < size; i++)
    {
        if (i == 59)
        {
            vec.push_back(1000000012);
        }
        else
        {
            vec.push_back(i);
        }
    }

    double startTime = getRealTime();
    int maxIter = *std::max_element(vec.begin(), vec.end());
    double stopTime = getRealTime();
    double totalIteratorTime = stopTime - startTime;

    startTime = getRealTime();
    int maxArray = my_max_element(vec, size);
    stopTime = getRealTime();
    double totalArrayTime = stopTime - startTime;

    std::cout << "MaxIter = " << maxIter << std::endl;
    std::cout << "MaxArray = " << maxArray << std::endl;
    std::cout << "Total CPU time iterator = " << totalIteratorTime << std::endl;
    std::cout << "Total CPU time array = " << totalArrayTime << std::endl;
    std::cout << "iter/array ratio: = " << totalIteratorTime / totalArrayTime << std::endl;
    return 0;
}

Output:

MaxIter = 1000000012
MaxArray = 1000000012
Total CPU time iterator = 0.000989199
Total CPU time array = 0.000293016
iter/array ratio: = 3.37592

一般std::max_element则需要 x3 多时间my_max_element。 那么为什么我能够如此轻松地创建一个更快的 std 函数呢?由于 std 太慢了,我应该停止使用 std 并编写自己的函数吗?

注意:起初我以为这是因为我正在使用 and 整数i在 for 循环而不是迭代器中,但这现在似乎并不重要。

编译信息:

g++(海湾合作委员会)4.8.2

g++ -O3 -Wall -c -fmessage-length=0 -std=c++0x


在对此答案进行投票之前,请在您的计算机上测试(并验证)并评论/添加结果。请注意,我在测试中使用的向量大小为 1000*1000*1000。目前,该答案有 19 条赞成票,但只有 1 条发布结果,并且这些结果没有显示出下面描述的效果(虽然是通过不同的测试代码获得的,请参阅评论)。


似乎存在优化器错误/工件。比较以下时间:

template<typename _ForwardIterator, typename _Compare>
_ForwardIterator
my_max_element_orig(_ForwardIterator __first, _ForwardIterator __last,
_Compare __comp)
{
  if (__first == __last) return __first;
  _ForwardIterator __result = __first;

  while(++__first != __last)
    if (__comp(__result, __first))
      __result = __first;

  return __result;
}

template<typename _ForwardIterator, typename _Compare>
_ForwardIterator
my_max_element_changed(_ForwardIterator __first, _ForwardIterator __last,
_Compare __comp)
{
  if (__first == __last) return __first;
  _ForwardIterator __result = __first;
  ++__first;

  for(; __first != __last; ++__first)
    if (__comp(__result, __first))
      __result = __first;

  return __result;
}

第一个是原始 libstdc++ 实现 https://gcc.gnu.org/onlinedocs/gcc-4.9.1/libstdc++/api/a01499_source.html#l05478,第二个应该是一种没有任何行为或要求变化的转变。 Clang++ 为这两个函数生成非常相似的运行时间,而 g++4.8.2 的第二个版本快了四倍。


根据Maxim的建议,将矢量从int to int64_t,改变后的版本不是4,只是比原始版本(g++4.8.2)快1.7倍。


区别在于预测的共同点*result,即存储当前最大元素的值,这样就不必每次都从内存中重新加载。这提供了更清晰的缓存访问模式:

w/o commoning     with commoning
*                 *
**                 *
 **                 *
  **                 *
  * *                 *
  *  *                 *
  *   *                 *

这是用于比较的汇编(rdi/rsi分别包含第一个/最后一个迭代器):

使用 while 循环(2.88743 ms;gist https://gist.github.com/ecatmur/8c0b50dc4e30e1206a98#file-max_element-c):

    movq    %rdi, %rax
    jmp .L49
.L51:
    movl    (%rdi), %edx
    cmpl    %edx, (%rax)
    cmovl   %rdi, %rax
.L49:
    addq    $4, %rdi
    cmpq    %rsi, %rdi
    jne .L51

使用 for 循环(1235.55 μs):

    leaq    4(%rdi), %rdx
    movq    %rdi, %rax
    cmpq    %rsi, %rdx
    je  .L53
    movl    (%rdi), %ecx
.L54:
    movl    (%rdx), %r8d
    cmpl    %r8d, %ecx
    cmovl   %rdx, %rax
    cmovl   %r8d, %ecx
    addq    $4, %rdx
    cmpq    %rdx, %rsi
    jne .L54
.L53:

如果我通过显式存储来强制共用*result到一个变量中prev在开始时以及每当result已更新,并使用prev代替*result在比较中,我得到了更快的循环(377.601 μs):

    movl    (%rdi), %ecx
    movq    %rdi, %rax
.L57:
    addq    $4, %rdi
    cmpq    %rsi, %rdi
    je  .L60
.L59:
    movl    (%rdi), %edx
    cmpl    %edx, %ecx
    jge .L57
    movq    %rdi, %rax
    addq    $4, %rdi
    movl    %edx, %ecx
    cmpq    %rsi, %rdi
    jne .L59
.L60:

The reason this is faster than the for loop is that the conditional moves (cmovl) in the above are a pessimisation as they are executed so rarely (Linus says http://yarchive.net/comp/linux/cmov.html that cmov is only a good idea if the branch is unpredictable). Note that for randomly distributed data the branch is expected to be taken Hn http://en.wikipedia.org/wiki/Harmonic_number times, which is a negligible proportion (Hn grows logarithmically, so Hn/n rapidly approaches 0). The conditional-move code will only be better on pathological data e.g. [1, 0, 3, 2, 5, 4, ...].

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

为什么 c++ std::max_element 这么慢? 的相关文章

随机推荐

  • 多个表的视图。需要删除 1 个表定义的“双打”

    好吧 这就是我所坚持的 Full size https i stack imgur com AEIjH png SELECT dbo InstellingGegevens INST SUBTYPE dbo InstellingGegeven
  • 如何绘制这些数据?

    我有一个值数组theta and phi 如何轻松创建 MATLAB 绘图 其中theta and phi是这样的球坐标 如果我有一个数组 如何在 MATLAB 中绘制值theta and phi半径保持不变 这些是theta theta
  • pandas 时间戳与日期时间的性能较慢

    我似乎遇到了 pandas Timestamp 与 python 常规 datetime 对象的算术运算性能出乎意料的缓慢 这是一个基准测试 演示了 import datetime import pandas import numpy us
  • 找不到模块:无法解析“swiper/react”

    我在使用最新版本的 Swiper 时也遇到了同样的问题 它在我之前的项目中有效 但现在不起作用 连那个版本都没有 最新版本也试过了 这是我的代码 Import Swiper React components import Swiper Sw
  • xtext 自定义作用域:函数参数

    我正在尝试自定义作用域 这样 如果我的语言中有类似函数的东西可以获取参数 我希望这些参数仅在出现分号之前才可见 而在这个范围之外 我希望它不可见 我尝试在文件 MyDslScopeProvider xtend 中重新定义方法 getScop
  • 在我获取输入数据之前,什么会对其进行重新格式化?

    我有一个数据湖存储帐户 我有一个充满包含 JSON 格式数据的文件的目录 其中包括一些包含 ISO 8601 格式时间的字符串值 即 reading time 2008 09 15T15 53 00 91077 现在 当我使用数据工厂创建管
  • Android中保存同一个Activity的多个实例状态

    我正在开发一个 Android 应用程序 当数据来自使用相同额外内容的相同 Activity 时 我希望避免重新加载类似数据 具体来说 当我使用 extra 启动 Activity A 时 我使用这个 extra 从服务器加载远程数据 通过
  • Android Studio:“Gradle 同步失败:无法从选定的 JDK 运行 JVM。”

    自从安装Android Studio 3 2后 我一直无法运行Java 我尝试过jdk 8u181 windows x64 jdk 10 0 2 windows x64 bin jdk 11 windows x64 bin 环境PATH并重
  • 映射到同类 Traversable 的 Traversable 类型

    简洁版本 Scala 中的大多数泛型集合都有一个map实际上 该方法将返回相同类型的集合 List A map f A gt B 返回一个List B 例如 Scala 集合库就是为了实现这一目标而明确设计的 如果我想编写对任何此类集合具有
  • 使用 Selenium 上传文件失败

    我正在尝试使用 Selenium 在 Eclipse 上使用以下代码将文件上传到表单 search driver find element by xpath input type file search send keys D test t
  • 使用 image.complete 查找图像是否缓存在 chrome 上?

    我一直试图找出外部图像是否用js缓存在浏览器上 这是我到目前为止的代码
  • XML 转换导致 FileNotFoundException

    由于缺乏信息 我之前发布的问题已关闭 如果我在这里遗漏了什么 请告诉我 转换器似乎将 file 添加到我的文件路径的开头 我在 Solaris 环境中工作 应用转换时会发生以下情况 DOMSource sourcexml new DOMSo
  • Android模拟器无法创建上下文0x3005

    我对 Android 开发完全陌生 我正在尝试在 Android 中执行一个小任务 注册表单 但是 我收到以下错误 2013 12 05 11 06 26 Emulator could not get wglGetExtensionsStr
  • C++ 随机猜数字游戏

    我必须编写一个程序来运行随机猜谜游戏 游戏的数字是从 1 到 100 猜测者可以尝试 20 次 最后应该被问是否愿意再玩一次 如果猜测者高或低 还必须有多种打印输出选项 我已经完成了程序的一部分 我知道我仍然需要添加其他打印输出选项 但现在
  • 解释UnixTime毫秒

    我正在尝试找到更好的方法在 C 中将 DateTime 转换为 unix 时间戳 我发现有一个 DateTimeOffset ToUnixTimeMilliseconds 方法 public long ToUnixTimeMilliseco
  • MvxCachingFragmentCompatActivity消失了吗?

    我正在尝试升级到 MvvmCross 5 2 但在 MvxCachingFragmentCompatActivity 上出现语法错误 这个班级消失了吗 正如 5 2 博客中所解释的 https www mvvmcross com mvvmc
  • 如何更新d3表?

    鼠标移动时更新 d3 js 表时遇到一些问题 这是一个简化的example https jsfiddle net lszhou2115 npzjLng9 6 在jsfiddle中 这是主要代码 function mousemove var
  • 如何修复 ubuntu 中的“没有名为‘kivy._clock’的模块”错误?

    我正在尝试使用 Ubuntu 16 04 for Python 3 6 安装 kivy GUI lib 我尝试执行kivy官方网站中的步骤 https kivy org doc stable installation installatio
  • GAE java中通过证书进行客户端身份验证

    我正在写一份申请GAE java通过其身份验证用户证书 我已经使用创建了一个自签名证书keytool在客户端 我还在 Google 应用程序引擎中为我的应用程序启用 https 请求 申请流程非常简单 用户使用任何浏览器进入应用程序的主页
  • 为什么 c++ std::max_element 这么慢?

    我需要找到向量中的最大元素 所以我使用std max element 但我发现它是一个非常慢的函数 所以我编写了自己的版本并设法获得 x3 更好的性能 下面是代码 include