【问题标题】:Memoizing a function with two inputs in C++在 C++ 中使用两个输入来记忆一个函数
【发布时间】:2012-04-11 19:33:59
【问题描述】:

我有一个函数f(a,b),它接受两个输入。我不知道将使用ab 的哪些值。我可以在内存上有点浪费(我关心速度)。我希望能够检查f(a,b) 的输出是否已经交付,如果是,请再次交付该输出,而无需重新运行f(a,b) 进程。

在 Python 中使用装饰器很容易做到,但在这里 C++ 实在是太过分了。

【问题讨论】:

  • 输入的数据类型是什么?
  • 无符号 64 位整数
  • 这是我写的generic memoizer
  • 到目前为止,这些答案似乎相当复杂......有没有办法我可以声明一个二维向量并以这种方式检索/设置?
  • @JohnSmith:你不能创建一个由单个 64 位整数索引的向量,更不用说两个。你需要2**128 元素!只是预初始化它会非常非常慢。

标签: c++


【解决方案1】:

我会使用一个std::map(或者可能是一个std::unordered_map),它的键是std::pair,或者可能使用一张地图。

在这种情况下,C++11 的改进可能会有所帮助。或者也许是一些 Boost 的东西。

【讨论】:

    【解决方案2】:

    发帖人问:

    我希望能够检查 f(a,b) 的输出是否已经交付,如果是,则再次交付该输出,而无需重新运行 f(a,b) 流程。

    在 C++ 中使用 std::map 非常简单。函数正好有两个参数,这意味着我们可以用std::pair来描述它们。

    #include <map>
    #include <iostream>
    
    uint64_t real_f(int a, int b) {
      std::cout << "*";
      // Do something tough:
      return (uint64_t)a*b;
    }
    
    uint64_t memo_f(int a, int b) {
      typedef std::pair<int, int> key;
      typedef std::map<key, uint64_t> map;
      static map m;
      key k(a,b);
      map::iterator it = m.find(k);
      if(it == m.end()) {
        return m[k] = real_f(a, b);
      }
      return it->second;
    }
    
    int main () {
      std::cout << memo_f(1, 2) << "\n";
      std::cout << memo_f(3, 4) << "\n";
      std::cout << memo_f(1, 2) << "\n";
      std::cout << memo_f(3, 4) << "\n";
      std::cout << memo_f(5, 6) << "\n";
    }
    

    上述程序的输出为:

    *2
    *12
    2
    12
    *30
    

    没有星号的行代表缓存的结果。

    【讨论】:

    • 小挑剔,m[k] 将进行另一次搜索,m.find(k) 已经执行过。为避免这种情况,请考虑改用m.insert()m.emplace()
    【解决方案3】:

    使用 C++11,您可以使用任务和未来。让f 成为你的函数:

    int f(int a, int b)
    {
        // Do hard work.
    }
    

    然后你会安排函数执行,它会返回一个返回值的句柄。这个句柄被称为future

    template <typename F>
    std::future<typename std::result_of<F()>::type>
    schedule(F f)
    {
        typedef typename std::result_of<F()>::type result_type;
        std::packaged_task<result_type> task(f);
        auto future = task.get_future();
    
        tasks_.push_back(std::move(task)); // Queue the task, execute later.
        return std::move(future);
    }
    

    那么,你可以像下面这样使用这个机制:

    auto future = schedule(std::bind(&f, 42, 43)); // Via std::bind.
    auto future = schedule([&] { f(42, 43); });    // Lambda alternative.
    
    if (future.has_value())
    {
        auto x = future.get();  // Blocks if the result of f(a,b) is not yet availble.
        g(x);
    }
    

    免责声明:我的编译器不支持任务/期货,因此代码可能有些粗糙。

    【讨论】:

    • 我认为这没有帮助。您仍然需要一种方法来查找与特定 (a, b) 元组相对应的 future 对象,如果这样做,您可以只查找结果。
    • @BenVoigt:如果f 便宜,我同意。但据我了解,f 的实际计算可能很昂贵。
    【解决方案4】:

    关于这个问题的要点是计算 f(a,b) 和保持某种查找表来缓存结果之间的 CPU 和 RAM 的相对开销。

    由于 128 位索引长度的详尽表(还)不可行,我们需要将查找空间减少到可管理的大小 - 如果没有在您的应用程序内部考虑一些因素,这是无法做到的:

    • 函数输入的实际使用空间有多大?里面有规律吗?
    • 时间组件呢?您是否希望重复计算彼此接近或沿时间线分布?
    • 分布情况如何?您是否假设索引空间的一小部分会消耗大部分函数调用?

    我会简单地从一个固定大小的 (a,b, f(a,b)) 元组数组和线性搜索开始。根据上述模式,您可能需要

    • 窗口滑动它(在缓存未命中时删除最旧的):这对本地化重复有好处
    • 拥有(a,b,f(a,b),count) 元组,其中计数最少的元组被驱逐 - 这对于非本地化事件很有用
    • 有一些关键功能确定缓存中的位置(这有利于小索引空间的使用)
    • 无论 Knuth 或 Google 想到了什么

    如果查找机制变得越来越复杂,您可能还想对重复计算进行基准测试:std::map 和朋友不是免费的,即使他们是高质量的实现。

    【讨论】:

      【解决方案5】:

      唯一简单的方法是使用std::mapstd::unordered_map 不起作用。我们不能使用std::pair 作为无序映射中的键。您可以执行以下操作,

      std::map<pair<int, int>, int> mp; 
      
      int func(int a, int b)
      {
        if (mp.find({a, b}) != mp.end()) return mp[{a, b}];
        // compute f(a, b)...
        mp[{a, b}] = // computed value;
        return mp[{a, b}];
      }
      

      【讨论】:

      • 为什么你认为我们不能使用std::unordered_map?也许应该提供一个哈希函数(我还没有检查过),就是这样。
      • 是的,我们不能使用 unordered_map,除非您为其提供哈希函数。
      猜你喜欢
      • 2015-09-05
      • 2021-12-23
      • 1970-01-01
      • 2019-06-02
      • 2010-10-12
      • 2017-11-02
      • 1970-01-01
      • 2013-06-14
      • 2011-07-29
      相关资源
      最近更新 更多