【问题标题】:How to quickly find maximal element of a sum of vectors?如何快速找到向量和的最大元素?
【发布时间】:2010-11-25 08:28:25
【问题描述】:

我的程序的最内部循环中有以下代码

struct V {
  float val [200]; // 0 <= val[i] <= 1
};

V a[600];
V b[250];
V c[250];
V d[350];
V e[350];

// ... init values in a,b,c,d,e ...

int findmax(int ai, int bi, int ci, int di, int ei) {
  float best_val = 0.0;
  int best_ii = -1;

  for (int ii = 0; ii < 200; ii++) {
    float act_val =
      a[ai].val[ii] +
      b[bi].val[ii] +
      c[ci].val[ii] +
      d[ci].val[ii] +
      e[ci].val[ii];

    if (act_val > best_val) {
      best_val = act_val;
      best_ii = ii;
    }
  }

  return best_ii;
}

我不在乎它是一些聪明的算法(但这会是最有趣的)还是一些 C++ 技巧或内在函数或汇编程序。但我需要让 findmax 函数更高效。

非常感谢。

编辑: 似乎分支是最慢的操作(预测错误?)。

【问题讨论】:

  • 你能告诉我们更多关于外循环的信息吗?或许与此相结合,优化的可能性会更大。
  • 微优化,这意味着它可以由编译器处理,但并没有真正受到伤害,而且我看到了相当令人惊讶的基准测试,说明它有时会产生多大的差异:将 i++ 切换为 ++一世。这样,值在递增之前永远不会被复制。

标签: c++ algorithm performance intrinsics


【解决方案1】:

如果编译器在缩短跳转时遇到困难,这可能会有所帮助:

int findmax(int ai, int bi, int ci, int di, int ei) {
  float best_val = 0.0;
  int best_ii = -1;

  float* a_it = &a[ai].val[0]
  float* b_it = &b[bi].val[0]
  float* c_it = &c[ci].val[0]
  float* d_it = &d[di].val[0] // assume typo ci->di
  float* e_it = &e[ei].val[0] // assume typo ci->ei

  for (int ii = 0; ii < 200; ii++) {
    float act_val = *(a_it++) + *(b_it++) + *(c_it++) + *(d_it++) + *(e_it++);
    best_val =  (act_val <= best_val) ? best_val : act_val; // becomes _fsel
    best_ii  =  (act_val <= best_val) ? best_ii : ii; // becomes _fsel
  }

  return best_ii;
}

就缓存未命中而言,生成总和表可能会更快,我稍后会发布:

int findmax(int ai, int bi, int ci, int di, int ei) {
  float best_val = 0.0;
  int best_ii = -1;

  float* its[] = {&a[ai].val[0], &a[bi].val[0], &a[ci].val[0], &a[di].val[0], &a[ei].val[0] };

  V sums;
  for (int ii = 0; ii < 200; ii++) {
    sums.val[ii] = * (++its[0]);
  }

  for (int iter = 1 ; iter < 5; ++iter)  {
      for (int ii = 0; ii < 200; ii++) {
        sums.val[ii] += * (++its[iter]);
      }
    }
  }
  for (int ii = 0; ii < 200; ii++) {
    best_val =  (sums.val[ii] <= best_val) ? best_val : sums.val[ii]; // becomes _fsel
    best_ii  =  (sums.val[ii] <= best_val) ? best_ii : ii; // becomes _fsel
  } 
  return best_ii;
}

【讨论】:

  • 如果您不喜欢我的方法,请尝试设置 bet_val 和 best_ii 的 _fsel 方法
【解决方案2】:

嗯,我认为算法优化没有明显的空间。理论上,只能计算五个向量的总和,直到明显无法达到最大值为止,但这会增加仅对五个数字求和的开销。您可以尝试使用多个线程并为线程分配范围,但是当您只有 200 个非常短的工作项时,您必须考虑线程创建开销。

所以我倾向于说,在 x86 上使用汇编程序和 MMX 或 SSE 指令,或者可能是(机器特定的)C++ 库,提供对这些指令的访问权限是您最好的选择。

【讨论】:

  • "您只有 200 个非常短的工作项。"尽管他说代码处于最内部的循环中,所以如果他针对 ai、bi 等的许多不同组合执行此操作,那么也许他可以多线程并在比此函数更高的级别上分解工作。取决于向量内容和每组 5 个参数是否取决于先前计算的结果。此外,它不是线程创建开销,而是线程通信开销,因为您可以维护一个工作线程池,而不是每次调用都创建它们。
  • 如果您将线程引入等式,您还必须考虑这是否真的有帮助,这取决于应用程序的更大目的以及它将在哪里运行。
  • 话虽如此,多线程永远不会让这个算法“更高效”,只是可能更快。它最终不会花费更少的 CPU 周期/操作来计算结果。多线程通常只有在机器上有空闲内核时才有帮助,例如运行大量应用程序的服务器,很可能没有。
【解决方案3】:

如果不检查每个总和,我看不出有任何方法可以做到这一点,这使得这是一个 O(n) 问题。但由于您的数据是线性布局的,因此 Intel/AMD MMX 或 SSE 指令可能会有所帮助。有关 Microsoft 内部函数的实现,请参阅此链接:

http://msdn.microsoft.com/en-us/library/y0dh78ez(VS.71).aspx

【讨论】:

  • 具体来说,您需要 addps(打包加法)指令,它实际上会同时执行 4 个浮点加法,将结果转储到相当于浮点数的 XMM 寄存器中[4]。如果您存储其中的一些,您还可以使用 maxps (packed max) 来进行并行比较。显然,最后几个比较必须使用单浮点运算而不是 SSE。
【解决方案4】:

除非编译器为您优化它们,否则在循环中计算 a[ai] 等会花费您一些时间(无论多么轻微),因为它们在 findmax 的持续时间内是固定的。鉴于此,您可以尝试以下方法:

int findmax(int ai, int bi, int ci, int di, int ei) {
    float    best_val = std::numeric_limits<float>::min();
    int      best_ii = 0;
    const V& a(a[ai]);
    const V& b(b[bi]);
    const V& c(c[ci]);
    const V& d(d[di]);
    const V& e(e[ei]);

    for (int ii = 0; ii < 200; ++ii) {
        float act_val = a.val[ii] + b.val[ii] + c.val[ii] +
                        d.val[ii] + e.val[ii];

        if (act_val > best_val) {
            best_val = act_val;
            best_ii = ii;
        }
    }

    return best_ii;
}

改进代码的其他方法可能是改变数据的表示方式,从而产生不同的(但速度更快)findmax 算法。

【讨论】:

  • 同意,函数内部没有太大的优化空间,但也许您多次找到相同的最大值,或者数据以您可以找到快捷方式的方式布局,这些是应该加快整个代码的速度。
  • 任何合理的编译器都会自动为您执行此优化。
  • best_val 应该初始化为负无穷
【解决方案5】:

尝试一次迭代所有向量。这是两个向量的示例:

for (float *ap = a[ai].val, *bp = b[bi].val; ap - a[ai].val < 200; ap++, bp ++) {
    float act_val = *ap + *bp;
    // check for max and return if necessary
}

【讨论】:

    【解决方案6】:

    看一下循环展开(以及 Duff 的特定设备,但要复杂得多,示例)。这些是我能想到的唯一真正的算法优化。

    Loop_unwinding

    Duff's_device

    【讨论】:

    • 当循环总是相同的长度(在本例中为 200)时,您实际上并不需要 Duff 的设备。要么使用 200 的因子作为展开长度,要么使用非因子但从一个 goto 开始进入循环中间。
    • 你是对的,你没有,但我认为它可以作为一个有趣的放松的例子。不过老实说,Duff 的设备比常规放松功能要多得多,我正在考虑将其从我的帖子中删除。
    • 我完全赞成每个人看到 Duff 的设备,只要他们知道除非绝对必要,否则不要使用它。甚至可能还没有:-)
    【解决方案7】:

    如果没有关于存储在 abcde 中的数据(值)的其他信息,您真的无法获得比这更快的速度。您必须检查每个总和以确定哪个最大。

    第 N 个元素的查询会更糟,但幸运的是,你没有问那个。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-13
      • 2012-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-18
      • 1970-01-01
      • 2016-11-09
      相关资源
      最近更新 更多