在非常小的数组中找到最小值

时间:2015-07-20 07:02:09

标签: c++ c arrays algorithm performance

我正在处理长int数据,我试图确定数组中的最小元素。我知道循环遍历数组的传统方法来找到最小值。这个问题是检查是否有其他方法可以加快速度。

这个数组的某些属性可能有助于我们加快速度,但我不确定如何。

该数组正好有8个长整数。每当我们调用该函数时,我们从数组中找到一个min,并且数字被另一个数字替换,我们重复这一步。 (至少80亿次)

我正在考虑记住下一次迭代的第二大数字(因为我们将在当前迭代中对它们进行比较)。与通过数组的线性实现相比,这是否有用?

也允许排序,但我们必须以某种方式记住使用临时数组的原始位置。这会更有效吗?

也可以用某种方式使用SIMD来确定最长的整数?即使是毫秒级的加速也很有用,因为我正在进行数十亿次这样的操作。

7 个答案:

答案 0 :(得分:5)

具有8个元素阵列的算法的理论复杂性几乎无关紧要。在给定缓存局部性和所有内容的情况下,线性搜索很可能是您的最佳选择。

另一种选择是按降序对数组进行一次排序,然后每次只更换第一个元素,最后在右边移动新数字。

无论如何,请尝试和个人资料。

答案 1 :(得分:1)

可以使用SIMD执行此操作,因为您可以并行化最多4个比较。循环遍历数组的常规算法无法进行矢量化,因为每次比较都取决于之前的比较结果,例如

x = min(array[0], array[1])
x = min(x, array[2])
x = min(x, array[3))
...

如果您将其更改为一种淘汰赛锦标赛方法,如果您将值0-3加载到一个向量中并将值4-7加载到另一个向量中,则可以立即进行多次比较:

// these 4 ops can be done at once using SIMD
x[0] = min(array[0], array[4])
x[1] = min(array[1], array[5])
x[2] = min(array[2], array[6])
x[3] = min(array[3], array[7])

// so can these 2 ops:
y[0] = min(x[0], x[2])
y[1] = min(x[1], x[3])

z[0] = min(y[0], y[1])

这意味着理论上只需要进行3次矢量化比较。

例如,在ARM NEON SIMD中,它看起来像这样(比较8个32位值):

vldm     r1!, {d0-d3}
vmin.32  q0, q0, q1    // first vectorized comparison
vpmin.32 d0, d0, d1    // second comparison
vpmin.32 d0, d0, d1    // third comparison
// min value is now in d0[0]

在上一次比较中,你最终会做一些你不需要的额外比较,因为它是矢量化的,但它并不重要。

我使用ARM NEON作为示例,因为我并不熟悉x86 SIMD,但是相同的方法应该可以工作并且可以扩展到64位值,如{{3} }

与往常一样,请确保您的资料,不要过早优化,yadda yadda yadda

答案 2 :(得分:0)

您可以以最小堆的形式组织数组。搜索结果为O(1),替换为O(logn)。这样可以提高从O(n)O(logn)的时间复杂度,这应该是重要的。

答案 3 :(得分:0)

因为它只有8个整数,所以按如下方式进行:

  1. 第一次对八个数字进行排序,保留原始索引
  2. 以"展开的方式实施二进制搜索" :这些是代码中的8个if / else行
  3. 使用二进制搜索查找最大数字
  4. 使用二进制搜索找到要插入的新整数的正确位置

答案 4 :(得分:0)

尝试使用min-heap。例如

#include <iostream>
#include <algorithm>
#include <array>
using namespace std;

int main() {
    array<int, 8> arr { 3, 1, 4, 6, 5, 9, 2, 7 };
    make_heap(arr.begin(), arr.end(), greater<int>());
    pop_heap(arr.begin(), arr.end());
    cout << "Min Element: " << arr.back() << endl;
    return 0;
}

<强>输出

1

这里天真的方式是

*min_element(arr.begin(), arr.end());

或者您可能使用multiset

std::multiset<long int> ms { 3, 1, 4, 6, 5, 8, 2, 7 };

for every new_element
    ms.erase(ms.begin());    // ms.begin() is the iterator to min element
    ms.insert(new_element);

答案 5 :(得分:0)

由于N非常小,并且替换过程本质上是连续的,因此很难在此操作上获得显着的加速。虽然从理论上讲,小堆是一个完美的工具,但由于开销,我不会赌它。

我的建议是保持阵列按升序排列,并在更换最小值时使用InsertionSort的插入步骤,即逐个将元素移到前面,直到找到插入槽。您可以完全展开代码以避免检查数组结束条件。

保持元素排序的好处是,一旦找到插入点,就可以停止搜索。平均而言,您可以预期在比较次数方面有所改善(但内存移动次数增加:-()

您还可以考虑使用二进制搜索来查找插入点,进行3或4次比较,但我怀疑它会明显优于线性搜索。

如果您的值适合16位无符号整数,那么您对_mm_minpos_epu16指令非常满意。

在完全偏执的版本中,您可以通过对将原始数组转换为排序序列的排列进行编号来避免不必要的内存移动。总共有40320个(!)。安排一个巨大的硬编码开关语句,在该语句中,给定手边的排列,以相关的顺序执行线性搜索;然后替换最大值并更新置换索引。

答案 6 :(得分:0)

我会保留一些信息并更新它。

您有八个值x0到x7。

保持值a0 = max(x0,x1),a2 = max(x2,x3),a4 = max(x4,x5),a6 = max(x6,x7),加上记住哪一个是每个中最大的一个对。

保持值b0 = max(a0,a2),b4 = max(a4,a6),并记住哪一个是每组中最大的一个。

现在获得最大元素是微不足道的。当你拥有它并插入一个新元素时,你需要更新其中一个值a0,a2,a4和a6,以及b0和b4中的一个。

(刚刚注意到你正在寻找最低限度 - 不应该有太大的区别)。