优化调用昂贵函数的次数

Posted

技术标签:

【中文标题】优化调用昂贵函数的次数【英文标题】:Optimizing the number of calls to expensive function 【发布时间】:2016-11-07 19:27:55 【问题描述】:

我有一个mainFun,它接受四个参数xabc,它们都是向量值并且可能具有不同的长度。这个函数调用expensiveFun 计算量很大,所以我想减少对expensiveFun 的调用次数。需要为x[i]a[i]b[i]c[i] 中的每个值调用此函数,如果abc 的长度较短,则它们需要为 "包裹”(它们的索引以 a[i % a.size()] 为模)。最好为 x 的每个可能的不同值(即所有整数 0,...,max(x))预先计算 expensiveFun,然后将输出 out 填入 out[i] = precomputedValues[x[i]] .如果abc 具有相同的长度(下面的示例),这可以很容易地实现,但如果它们不是,它会变得很难看。当参数向量的长度不同时,有什么方法可以提高效率?

下面我提供了一个可重现的例子。这是一个简化的代码,仅作为示例编写。

std::vector<int> expensiveFun(int x, int a, int b, int c) 
  std::vector<int> out(x+1);
  out[0] = a+b*c;
  for (int i = 1; i <= x; i++)
    out[i] = out[i-1] * i + a * (b+c);
  return out;


std::vector<int> mainFun(
    std::vector<int> x,
    std::vector<int> a,
    std::vector<int> b,
    std::vector<int> c
) 

  int n = x.size();
  int a_size = a.size();
  int b_size = b.size();
  int c_size = c.size();

  std::vector<int> out(n);

  // easy
  if (a_size == b_size && b_size == a_size) 

    int max_x = 0;
    for (int j = 0; j < n; j++)
      if (x[j] > max_x)
        max_x = x[j];

    for (int i = 0; i < a_size; i++) 
      int max_x = 0;
      for (int j = 0; j < n; j += a_size) 
        if (x[j] > max_x)
          max_x = x[j];
      
      std::vector<int> precomputedValues = expensiveFun(max_x, a[i], b[i], c[i]);
      for (int j = i; j < n; j += a_size) 
        out[j] = precomputedValues[x[j]];
      
    

  // otherwise give up
   else 

    for (int j = 0; j < n; j++) 
      out[j] = expensiveFun(x[j], a[j % a_size], c[j % c_size], c[j % c_size]).back();
    

  

  return out;

示例输入:

x = 0, 1, 5, 3, 2, 1, 0, 4, 4, 2, 3, 4, 1
a = 1, 2, 3
b = 1, 2
c = 3, 4, 5, 6

参数应该被折叠成:

x = 0, 1, 5, 3, 2, 1, 0, 4, 4, 2, 3, 4, 1
a = 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1
b = 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1
c = 3, 4, 5, 6, 3, 4, 5, 6, 3, 4, 5, 6, 3

目前输出并不重要,因为这里的主要问题是关于有效处理可变大小的参数向量。

【问题讨论】:

如果a_size == b_size 成立,通常b_size == a_size 也成立:-P 也许你的意思是c_size @j_random_hacker 确实是我的意思。 【参考方案1】:

Memoize你的函数。

一旦计算出 a、b 和 c 组合的向量,请将其存储在 std::unordered_map 中。下次您看到相同的组合时,您将检索您已经计算过的向量 - 用计算机内存付费以加快计算速度的经典方法。

std::map<std::tuple<int,int,int>,std::vector<int>> memo;

int expensiveFunMemo(int x, int xMax, int a, int b, int c) 
  assert(x <= xMax);
  std::vector<int>& out = memo[std::make_tuple(a, b, c)];
  if (!out.size()) 
    out.push_back(a+b*c);
    for (int i = 1; i <= xMax; i++)
      out.push_back(out[i-1] * i + a * (b+c));
  
  assert(out.size == xMax+1);
  return out[x];

这样,您将永远不会为a, b, c 的任意组合计算多次expensiveFunMemo

您的mainFun 也变得更简单:

std::vector<int> mainFun(
    const std::vector<int>& x,
    const std::vector<int>& a,
    const std::vector<int>& b,
    const std::vector<int>& c
) 
  size_t n = x.size();
  size_t a_size = a.size();
  size_t b_size = b.size();
  size_t c_size = c.size();
  std::vector<int> out(n);
  int xMax = *std::max_element(x.begin(), x.end());
  for (size_t j = 0 ; j < n ; j++) 
    out[j] = expensiveFunMemo(x[j], xMax, a[j % a_size], c[j % c_size], c[j % c_size]);
  
  return out;

注意:此解决方案使用std::map&lt;K,V&gt; 而不是std::unordered_map&lt;K,V&gt;,因为std::tuple&lt;...&gt; 缺少通用哈希函数。 This Q&A 提供了解决此问题的解决方案。

【讨论】:

这个编译应该没有错误吗?我得到error: no match for 'operator[]' (operand types are 'std::unordered_map&lt;std::tuple&lt;int, int, int&gt;, std::vector&lt;int&gt; &gt;' and 'std::tuple&lt;int, int, int&gt;') ... @Tim 我认为这是因为std::tuple&lt;...&gt; 缺少哈希函数。使用std::map 而不是std::unordered_map 应该可以解决这个问题。 谢谢,确实有帮助:)

以上是关于优化调用昂贵函数的次数的主要内容,如果未能解决你的问题,请参考以下文章

统计 Spark 中 UDF 的调用次数

Java如何控制方法的调用次数?

如何计算fibonacci函数的递归调用次数

C++:调用无参数的构造函数为啥不加括号

vtune 函数调用次数

chromium中的性能优化工具syzyProf