C++ 在数组中查找两个最小int64元素的最快方法

C++ 在数组中查找两个最小int64元素的最快方法,c++,optimization,minimum,C++,Optimization,Minimum,我有大小从1000到10000(1k..10k)的阵列。每个元素都是int64。我的任务是找到数组中两个最小的元素,最小元素和剩余元素中的最小元素 我想在C++中获得最快的单线程代码,英特尔CORE2或CORIE7(CPU模式为64位)。p> 这个函数(从数组中获取最小的2个)是热点,它嵌套在两个或三个循环中,用于迭代次数巨大的循环 当前代码如下所示: int f() { int best; // index of the minimum element int64 min_co

我有大小从1000到10000(1k..10k)的阵列。每个元素都是int64。我的任务是找到数组中两个最小的元素,最小元素和剩余元素中的最小元素

<>我想在C++中获得最快的单线程代码,英特尔CORE2或CORIE7(CPU模式为64位)。p> 这个函数(从数组中获取最小的2个)是热点,它嵌套在两个或三个循环中,用于迭代次数巨大的循环

当前代码如下所示:

int f()
{
    int best; // index of the minimum element
    int64 min_cost = 1LL << 61;
    int64 second_min_cost = 1LL << 62;
    for (int i = 1; i < width; i++) {
     int64 cost = get_ith_element_from_array(i); // it is inlined
     if (cost < min_cost) {
        best = i;
        second_min_cost = min_cost;
        min_cost = cost;
     } else if (cost < second_min_cost) {
        second_min_cost = cost;
     }
    }
    save_min_and_next(min_cost, best, second_min_cost);
}
intf()
{
int best;//最小元素的索引

int64 min_cost=1LL您拥有的是
O(n)
,对于随机数据来说是最佳的。这意味着,您已经拥有了最快的


改善这一点的唯一方法是为数组指定某些属性,例如,始终保持数组的排序或使其成为一个堆。

好的一点是,您的算法只扫描一次数字。您是最佳的

<> P>一个重要的慢度来源可能来自元素排列方式,如果它们是数组,我是指C数组(或者C++向量)如果所有元素都是连续的,并且你向前扫描它们,那么内存方面你也是最优的。否则,你可能会有一些惊喜。例如,如果你的元素在一个链表中,或者分散在一起,那么你可能会受到内存访问的惩罚。

查看并

std::vector arr(10000);//大
部分排序(arr.begin(),arr.begin()+2,arr.end());
//arr[0]和arr[1]是最小的两个值

如果您只需要第二个最低值,那么第n个元素就是您的对象

请确保您的数组读取行为正常,这样就不会引入不必要的缓存未命中

假设阵列读取很简单,则此代码可能非常接近现代CPU:s上的带宽限制。您需要分析和/或计算它是否仍有CPU优化空间。

尝试反转if:

if (cost < second_min_cost) 
{ 
    if (cost < min_cost) 
    { 
    } 
    else
    {
    }
} 
if(成本
您可能应该使用相同的值初始化min_cost和second_min_cost,使用int64的最大值(或者更好地使用qbert220的建议)

一些小事情(可能已经发生了,但我想可能值得一试)

  • 稍微展开循环-例如,以8的步幅迭代(即一次迭代缓存线),预取正文中的下一个缓存线,然后处理8项。为避免大量检查,请确保结束条件是8的倍数,剩余项(小于8)应在循环外处理-展开

  • 对于不感兴趣的项目,您在正文中进行了两次检查,可能是您可以将成本调整为1?即,如果
    成本小于
    秒分钟
    ,则也检查
    分钟
    ,否则无需麻烦


  • 您最好先检查second_min_cost,因为这是修改结果所需的唯一条件。这样,您将在主循环中得到一个分支,而不是两个分支。这应该会有很大帮助

    除此之外,没有什么可以优化的,您的已接近最优。展开可能会有所帮助,但我怀疑它在这种情况下是否会带来任何显著优势

    因此,它变成:

    int f()
    {
        int best; // index of the minimum element
        int64 min_cost = 1LL << 61;
        int64 second_min_cost = 1LL << 62;
        for (int i = 1; i < width; i++) {
        int64 cost = get_ith_element_from_array(i); // it is inlined
        if (cost < second_min_cost)
        {
          if (cost < min_cost) 
          {
            best = i;
            second_min_cost = min_cost;
            min_cost = cost;
          } 
          else second_min_cost = cost;
        }
        save_min_and_next(min_cost, best, second_min_cost);
    }
    
    intf()
    {
    int best;//最小元素的索引
    
    int64 min_cost=1LL OP显然对
    O(n)
    范围内的优化感兴趣,5*n操作和10*n操作都是
    O(n)
    ,但其中一个显然比另一个快。简单的大O符号分析在这里似乎不够。你可以用数组中的第一个条目初始化最小成本。我还注意到你目前只循环一次(宽度-1)时间,这可能不是预期的行为。最好使用数组的前两个元素初始化min_cost和second_min_cost,从i=2开始循环(当然,这是假设数组至少有两个元素)我认为这在很大程度上取决于
    从数组中获取元素的功能。如果它实际访问的数组大小
    宽度
    ,那么应该考虑缓存行为(特别是,如果要循环超过10k的内存数百万次,那么可能会有一些重叠,因此最重要的优化可能是为这个循环之外的2或3个循环选择最佳顺序)。如果它是从
    i
    计算值,那么内存性能可能完全无关。Steve的“从数组中获取元素”如下所示:“
    返回m[global\u j][i]-n[i]
    ”@osgx:因此,如果
    global_j
    在这个内部循环的不同运行之间有所不同,那么通过确保以
    global_j
    的相等值连续运行,您可能会得到一个很好的优化。这样,当您再次使用它时,
    m[global_j]
    仍将被缓存应该在循环之外处理-展开…-以及开始时达夫的设备!@Steve:我认为达夫的设备(以及手动展开)已被现代编译器淘汰:)?@Matthieu:有时手动展开(带或不带达夫)对于给定的基准测试或给定的实际用途,提供比优化器更快的代码。现代优化技术所取得的成就是使您无法自信地预测它是否有帮助,考虑到总是会有病态的用例来击败特定的优化,这一点与它所得到的一样好Steve Jessop,作为一名对编译器内部有点了解的程序员,我可以说Duff设备对编译器来说是一场噩梦,因为它是非常非线性的(在控制流图中)。大多数编译器都会尝试检测达夫并将其回滚到正常循环。甚至有时Xfree也会用简单的循环替换所有达夫。+1,你说得对,我误读了文档
    int f()
    {
        int best; // index of the minimum element
        int64 min_cost = 1LL << 61;
        int64 second_min_cost = 1LL << 62;
        for (int i = 1; i < width; i++) {
        int64 cost = get_ith_element_from_array(i); // it is inlined
        if (cost < second_min_cost)
        {
          if (cost < min_cost) 
          {
            best = i;
            second_min_cost = min_cost;
            min_cost = cost;
          } 
          else second_min_cost = cost;
        }
        save_min_and_next(min_cost, best, second_min_cost);
    }