C++“无原始循环”而不损失性能

2024-03-13

所以“新(旧)大事”是 C++ 中的“无原始循环”。我正在尝试以这种方式编写代码,但似乎效率很低。是的,有些 STL 算法可以做任何事情,但它们似乎效率不高。

例如,我有一种情况,我想要一个指向节点数组中得分最高的节点的指针。确定该分数是一项代价高昂的浮点运算。所以我实现了STL算法版本并将其与原始循环进行了比较:

#include <cfloat>
#include <iostream>
#include <array>
#include <algorithm>
#include <numeric>

static int counter;

class Node {
public:
    auto Score() const -> double {
        std::cout << "complex calculation\n";
        counter++;
        return 1;
    }
};

int main()
{
    
    std::array<Node, 10> nodes;
    
    counter = 0;
    Node const* nodePtr = std::max_element(std::cbegin(nodes), std::cend(nodes),
        [](Node const& node1, Node const& node2) {
            return node1.Score() < node2.Score();
        });
    std::cout << "algorithm count " << counter << std::endl;
    
    counter = 0;
    double maxScore = -FLT_MAX;
    for (const auto& node : nodes) {
        auto score = node.Score();
        if (score > maxScore) {
            maxScore = score;
            nodePtr = &node;
        }
    }
    std::cout << "raw loop count " << counter << std::endl;
}

对此进行评估,对于 STL 版本,昂贵的 Score 函数被评估了 18 次,而原始循环仅使用了 10 次评估......

我做错了吗,还是原始循环并没有那么糟糕?

编辑: 经过建议后user58697cout 和静态计数器会阻止编译器优化,我更改了代码:

#include <cfloat>
#include <cmath>
#include <iostream>
#include <array>
#include <algorithm>
#include <numeric>
#include <random>
#include <chrono>

template <typename T>
class Random {
private:
    std::default_random_engine generator;
    std::uniform_real_distribution<T> distribution;
public:
    Random()
        : generator()
        , distribution(0.0, 1.0)
    {}

    auto operator()() {
        return distribution(generator);
    };
};

static Random<double> myRandom;

class Timer {
private:
    std::chrono::high_resolution_clock::time_point startTime{};
public:
    void Start() noexcept {
        startTime = std::chrono::high_resolution_clock::now();
    }
    [[nodiscard]] auto ElapsedMs() const noexcept {
        return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count();
    }
};

static Timer timer;

class Node {
private:
    double val;
public:
    Node() noexcept : val(myRandom()) {}

    [[nodiscard]] auto Score() const noexcept {
        auto score = std::sqrt(std::log(10.0 / val));
        score = std::sin(score) / std::cos(score);
        score = std::sqrt(std::sqrt(std::sqrt(std::sqrt(std::sqrt(score)))));
        score = std::pow(score, 1000);
        return score;
    }
};

int main()
{
    std::array<Node, 100000> nodes; // yeah, yeah... overloading the stack, I know

    for (auto i = 0; i < 2; i++) {
        timer.Start();
        Node const* nodePtr = &*std::max_element(std::cbegin(nodes), std::cend(nodes),
            [](Node const& node1, Node const& node2) {
                return node1.Score() < node2.Score();
            });
        std::cout << "algorithm elapsed time " << timer.ElapsedMs() << std::endl;

        timer.Start();
        double maxScore = -FLT_MAX;
        for (const auto& node : nodes) {
            auto score = node.Score();
            if (score > maxScore) {
                maxScore = score;
                nodePtr = &node;
            }
        }
        std::cout << "raw loop count " << timer.ElapsedMs() << std::endl;
    }
}

我运行循环两次以消除启动行为...第二次循环的结果(用 g++ 9.1 -O3 编译):

algorithm elapsed time 16
raw loop count 8 (<== I see I forgot to change "count" to "time" :P)

所以不是这样的。

编辑: 看到这个问题得到了点赞,人们还在关注。 自从提出这个问题后,C++20就发布了。 C++20 的范围库有一个特殊功能,可以在这里提供帮助,称为投影 http://www.modernescpp.com/index.php/projections-with-ranges.

IE。在这种情况下你可以使用std::ranges::max_element https://en.cppreference.com/w/cpp/algorithm/ranges/max_element甚至std::ranges::max https://en.cppreference.com/w/cpp/algorithm/ranges/max(旧的标准算法中缺少这一点)就像

Node const* node = &*std::ranges::max_element(nodes, {}, &Node::Score);
...
Node const& node = std::ranges::max(nodes, {}, &Node::Score);

然而,由于实现选择不使用缓存,投影并不是这里的解决方案。这Proj投影函数被一次又一次地调用every的论点Comp比较器功能。

(内部函数调用看起来像

return std::invoke(__comp, std::invoke(__proj, __a), std::invoke(__proj, __b)) ? __b : __a;

)


用抽象算法替换原始循环是一种很好的风格,因为这样您就可以多次重复使用该算法,但只测试一次。以这种方式包装循环可能看起来像语法糖,但它大大减少了代码中出现错误的可能性,因为您现在可以对抽象算法进行广泛的单元测试,并且您永远不需要担心在需要时错误地实现它。

然而,您在这里比较的是苹果和橙子。你的max_element实现总是计算Score()进行比较,而你的for循环缓存结果Score()功能。

更好地实施Node可能:

class Node {
mutable:
    double cached_score = std::numeric_limits<double>::quiet_Nan();
public:
    auto Score() const -> double {
        if(std::isnan(cached_score)){
           std::cout << "complex calculation\n";
           counter++;
           cached_score = 1;
        }
        return cached_score;
    }
    void invalidate_cache() {
      cached_score = std::numeric_limits<double>::quiet_Nan();
    }
};

这样复杂的计算只执行一次。

或者,编写您自己的抽象:

#include <cfloat>
#include <iostream>
#include <array>
#include <algorithm>
#include <numeric>

static int counter;

class Node {
public:
    auto Score() const -> double {
        std::cout << "complex calculation\n";
        counter++;
        return 1;
    }
};

template<class ForwardIt, class Evaluate, class Compare>
ForwardIt max_eval_element(
    ForwardIt first,
    ForwardIt last,
    Evaluate eval,
    Compare comp
){
    if (first == last) return last;

    ForwardIt largest = first;
    auto largest_val = eval(*first);
    ++first;
    for (; first != last; ++first) {
        const auto this_val = eval(*first);
        if (comp(largest_val, this_val)) {
            largest = first;
            largest_val = this_val;
        }
    }
    return largest;
}

int main()
{

    std::array<Node, 10> nodes;

    counter = 0;
    Node const* nodePtr = max_eval_element(std::cbegin(nodes), std::cend(nodes),
                                       [](Node const& node){ return node.Score(); },
                                       [](double const &a, double const &b) {
                                           return a<b;
                                       });
    std::cout << "algorithm count " << counter << std::endl;

    counter = 0;
    double maxScore = -FLT_MAX;
    for (const auto& node : nodes) {
        auto score = node.Score();
        if (score > maxScore) {
            maxScore = score;
            nodePtr = &node;
        }
    }
    std::cout << "raw loop count " << counter << std::endl;
}

在这种情况下,两个循环执行相同数量的评估。

我使用过的许多内部代码库都有扩展 STL 的扩展库。它让我工作过的团队更加确信他们的代码已正确编写,并允许您一目了然地解释复杂的操作。这样一来,这些抽象也减少了理解代码的工作量和沟通的工作量。

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

C++“无原始循环”而不损失性能 的相关文章

随机推荐

  • Tweepy:现在可以使用 Twitter 搜索 api 获取旧推文了吗?

    根据http www theverge com 2014 11 18 7242477 twitter search now lets you find any tweet ever sent http www theverge com 20
  • 使用批处理文件按键盘按键[关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 我正在尝试开发一个批处理文件 它可以自动按向左箭头和向右箭头键 n 次 中间有一些暂停 有人可以帮我解决这个问题吗 P
  • 如何避免在 Scala 中调用 asInstanceOf

    这是我的代码的简化版本 怎样才能避免打电话asInstanceOf 因为这是一个设计不佳的解决方案的味道 sealed trait Location final case class Single bucket String extends
  • 使用框架会妨碍我掌握 JavaScript 吗?

    如果我一直用框架 自己什么都不做 我怎么能成为 JavaScript 高手呢 我问了一个关于 JavaScript 的问题 每个人都建议我使用框架 它不会向我展示 JS 的真正核心 而不是手动编码 你在自己发现JS的时候有没有编写自己的所谓
  • JavaFX 嵌套控制器 (FXML )

    In this http docs oracle com javafx 2 api javafx fxml doc files introduction to fxml html nested controllers教程中 有一个示例说明如
  • PHP foreach 循环中的多个索引变量

    是否有可能有一个foreach在 PHP 中使用多个 索引 变量循环 类似于以下内容 未使用正确的语法 foreach courses as course sections as section 如果没有 有没有好的方法可以达到相同的结果
  • springboot + webpack 开发服务器,重建后不会更改 localhost 捆绑文件

    点击这张图片 请阅读下面的内容 https i stack imgur com BYXDA png 1 第一张图片是运行 webpack dev server hot inline 之后的 第二张图片是我的html 我调用js文件的方式 我
  • 如何在 Xcode 8/Swift 3 中创建 iOS liveView [重复]

    这个问题在这里已经有答案了 我不知道如何在 Xcode 8 Swift 3 Playground 中创建和显示实时视图 如果 Apple 有关于 Playground 和实时视图的综合文档 我找不到它 而且我所有的在线搜索都显示 Xcode
  • 为什么OpenGL最初要设计成状态机?[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 处理 RequireJS require 函数中的先决条件加载失败

    我正在使用 AMD 的 RequireJS 使用这段代码 我在确保module1已加载 require module1 function module1 if module1 My function code 在某些情况下module1不可
  • 如何使用 ClientID 和 ClientSecret 在 Phonegap 内使用 Angularjs 登录 Google OAuth2

    我正在尝试使用 Angularjs 使用 Ionic 框架 通过 Google OAuth2 从我的 Phonegap 应用程序登录 目前我正在使用http phonegap tips com articles google api oau
  • Android蓝牙RSSI值总是返回-32768?

    我试图通过单击按钮获取已连接蓝牙设备的当前 RSSI 值 然而它总是只返回 32768 不知道出了什么问题 不过 我在第一次连接时就能够获得正确的 RSSI private Button OnClickListener buttonRSSI
  • 如何在反应本机模式中调暗背景?

    以下是我创建的反应本机模态 但仍然找不到如何调暗背景并在弹出模态周围透明 我没有使用任何外部库 并试图在没有库的情况下找到解决方案 是否可以用这种方式来做 我的模态组件 render let modal this state modalTy
  • Xpath 获取第二个 url 以及 href 标签中的匹配文本

    一个html页面有分页链接 1个设置在页面顶部 另一个设置在页面底部 使用 HtmlUnit 我目前正在使用页面上获取 HtmlAnchorgetByAnchorText 1 顶部的某些链接存在问题 因此我想使用 XPath 引用底部链接
  • svn 与 git 浅(稀疏)签出 - 分支、提交

    我有一个非常大的网络项目 有很多 pdf 图像 php 文件 我将文件作为单个项目导入到 svn 中 我使用 svn 浅签出来签出子树的一部分 然后使用工作副本中的分支和标记等来节省空间并加快签出时间 我想知道这是否可以用 git 实现 我
  • 如何在IOS7中使#key和@key可点击

    任何人都知道如何在 IOS7 中的评论文本中使 KEY 和 NAME 可点击 例如 instagram 的做法相同 我正在尝试使用 NSMutableAttributedString 但我不确定如何检测单击事件 在下图中单击 Usernam
  • Windows 上与 taglib 的链接错误

    I built taglibWindows 的静态库如下 必须使用mingw 而不是VS 查看git clone https github com taglib taglib git git taglib 已安装cmake使用来自 cmak
  • 用C++设计事件机制

    我试图在 C 中设计一个通用的 但有些特定于用例的 事件传递机制 而不违背 新风格 C 的原则 同时又不过度使用模板 我的用例有些特殊 因为我需要完全控制事件的分发时间 事件系统是世界模拟的基础 其中世界的每次迭代都会作用于前一帧生成的事件
  • 使用 BOOST 进程在单独的线程中读取子进程标准输出

    我有一个主程序 它使用 boost 进程库来生成一个打印的子进程 Hello World 每 5 秒在其标准输出上一次 我想在主进程中的子进程的标准输出可用时读取 监视它 并在主程序中执行其他操作 我已经尝试过这些例子boost async
  • C++“无原始循环”而不损失性能

    所以 新 旧 大事 是 C 中的 无原始循环 我正在尝试以这种方式编写代码 但似乎效率很低 是的 有些 STL 算法可以做任何事情 但它们似乎效率不高 例如 我有一种情况 我想要一个指向节点数组中得分最高的节点的指针 确定该分数是一项代价高