【问题标题】:What kind of optimization would increase my performance?什么样的优化会提高我的性能?
【发布时间】:2016-06-29 12:47:48
【问题描述】:

我编写了一个小型 java 程序,它对排序算法(合并排序、选择排序)进行基准测试,并显示他们需要对人员对象进行排序(名称、升序)的时间。

这个程序的 C++ 版本至少比 java 版本慢 4 倍。有几个开发人员说他们通过优化代码在排序方面击败了 java。释放模式,/O2,64 位,...。我已经完成了这些调整。

我的排序算法(尤其是归并排序)是否存在效率低下的问题?

//create a subvector
template <typename T> std::vector<T> splitVec(std::vector<T> main, size_t from, size_t to) {

std::vector<T>::const_iterator first = main.begin() + from;
std::vector<T>::const_iterator last = main.begin() + to;
std::vector<T> erg(first, last);

return erg;
}

//merge sort - sorting process
template <typename T> std::vector<T> merge(std::vector<T> m1, std::vector<T> m2) {

unsigned int posA = 0, posB = 0;

std::vector<T> erg;

while (posA < m1.size() && posB < m2.size()) {
    if (m1.at(posA).compareTo(m2.at(posB)) <= 0) {
        erg.push_back(m1.at(posA));
        posA++;
    }
    else {
        erg.push_back(m2.at(posB));
        posB++;
    }
}

while (posA < m1.size()) {
    erg.push_back(m1.at(posA));
    posA++;
}

while (posB < m2.size()) {
    erg.push_back(m2.at(posB));
    posB++;
}

return erg;
}

//merge sort-split up vectors
template <typename T> std::vector<T> mergeSort(std::vector<T> pers) {

if (pers.size() > 1) {

    //Split pers into two equally sized vectors
    std::vector<T> p1(splitVec(pers, 0, pers.size()/2));
    std::vector<T> p2(splitVec(pers, (pers.size()/2), pers.size()));

    return merge(mergeSort(p1), mergeSort(p2));
}
else
    return pers;
}

提前致谢

【问题讨论】:

  • 您正在按值传递和返回向量。在 C++ 中,这会复制向量的所有元素。
  • 代码结构中的优化通常优于编译器优化设置。通过引用而不是按值传递向量将是一个很好的第一步。 reserve()ing 前面所需的空间量很容易。仅这两个就应该至少让您进入与 Java 相同的范围。公平地说,至少你没有new 每个对象,所以有希望。 ;-)
  • minor: at 进行绑定检查,而 [] 没有(并且您已经检查了索引有效性:-))。
  • 我已经通过引用传递了所有内容,并在所有可能的地方保留了空间。我的程序现在运行得更快了:)
  • c++ 版本的性能大大优于 java 版本。 :)))

标签: c++ performance sorting optimization


【解决方案1】:

不要传递向量。不是按价值,也不是按参考。传递迭代器:

template <class Iter>
void sort(Iter first, Iter last) {
    ...
}

sort(my_vector.begin(), my_vector.end();

要分割一个范围,只需计算中间值:

template <class Iter>
Iter mid(Iter first, Iter last) {
    return first + (last - first) / 2;
}

这假定代码仍在对保存在某种容器中的值进行排序(在原始代码中,std::vector),因此迭代器是随机访问迭代器。

【讨论】:

  • 我会尝试,但我从未听说过迭代器。
  • @TalipVural - 迭代器是 STL 三部曲的一部分:迭代器、算法、容器。它们是 C++ 标准库大部分功能的基础。深入研究该主题将是非常值得的。 Here's a link 我刚刚查找的教程。不知道好不好用,不过一看就知道有前途。
【解决方案2】:

通过引用传递向量。这应该会显着提高性能。

当您通过值传递向量时,您每次都复制它(在每一步中增加 O(n) 的复杂度)

【讨论】:

    【解决方案3】:

    通过引用传递源数据,而不是复制它,将是一个巨大的改进。

    此外,您应该在erg 中添加reserve 空格,否则您将在添加更多元素时反复重新分配和复制所有元素。

    【讨论】:

    • 真的会复制所有元素吗?
    • @tobi303 如果新元素不适合当前分配的空间,则将所有元素重新分配到内存中的新位置。
    • @tobi303:是的,确实如此。
    • 有道理,因为元素是连续存储的,但不知何故我没有意识到这一点
    【解决方案4】:

    使用 push_back 而不是索引很慢。一次分配工作数组或向量,并索引到该数组消除了所有这些递归分配。使用一对相互递归的函数可以避免在合并后复制数据。

    自下而上的合并排序会稍微快一些,虽然自下而上的合并排序通常是大多数库(例如 std::stable_sort)所使用的,但自上而下的合并排序似乎是在课堂上教授的内容。

    适用于数组或向量的自上而下合并排序的示例模板(将向量作为指向第一个元素的指针传递)。

    template <typename T>
    void TopDownSplitMergeAtoA(T a[], T b[], size_t ll, size_t ee);
    template <typename T>
    void TopDownSplitMergeAtoB(T a[], T b[], size_t ll, size_t ee);
    template <typename T>
    void TopDownMerge(T a[], T b[], size_t ll, size_t rr, size_t ee);
    
    template <typename T>
    void MergeSort(T a[], size_t n)             // entry function
    {
        if(n < 2)                               // if size < 2 return
            return;
        T *b = new T[n];
        TopDownSplitMergeAtoA(a, b, 0, n);
        delete[] b;
    }
    
    template <typename T>
    void TopDownSplitMergeAtoA(T a[], T b[], size_t ll, size_t ee)
    {
        if((ee - ll) == 1)                  // if size == 1 return
            return;
        size_t rr = (ll + ee)>>1;           // midpoint, start of right half
        TopDownSplitMergeAtoB(a, b, ll, rr);
        TopDownSplitMergeAtoB(a, b, rr, ee);
        TopDownMerge(b, a, ll, rr, ee);     // merge b to a
    }
    
    template <typename T>
    void TopDownSplitMergeAtoB(T a[], T b[], size_t ll, size_t ee)
    {
        if((ee - ll) == 1){                 // if size == 1 copy a to b
            b[ll] = a[ll];
            return;
        }
        size_t rr = (ll + ee)>>1;           // midpoint, start of right half
        TopDownSplitMergeAtoA(a, b, ll, rr);
        TopDownSplitMergeAtoA(a, b, rr, ee);
        TopDownMerge(a, b, ll, rr, ee);     // merge a to b
    }
    
    template <typename T>
    void TopDownMerge(T a[], T b[], size_t ll, size_t rr, size_t ee)
    {
        size_t o = ll;                          // b[]       index
        size_t l = ll;                          // a[] left  index
        size_t r = rr;                          // a[] right index
        while(1){                               // merge data
            if(a[l] <= a[r]){                   // if a[l] <= a[r]
                b[o++] = a[l++];                //   copy a[l]
                if(l < rr)                      //   if not end of left run
                    continue;                   //     continue (back to while)
                while(r < ee)                   //   else copy rest of right run
                    b[o++] = a[r++];
                break;                          //     and return
            } else {                            // else a[l] > a[r]
                b[o++] = a[r++];                //   copy a[r]
                if(r < ee)                      //   if not end of right run
                    continue;                   //     continue (back to while)
                while(l < rr)                   //   else copy rest of left run
                    b[o++] = a[l++];
                break;                          //     and return
            }
        }
    }
    

    【讨论】:

    • 谢谢。我会和我的老师讨论你的版本。为什么要传递数组大小?你不能用 array.size() 来获取它吗?
    • 在 C / C++ 中,数组没有大小。向量有大小,但此模板是为数组设计的,因此对于向量,指向第一个元素的指针和向量的大小(元素数)作为参数传递给 MergeSort()..
    【解决方案5】:

    你错过了一个非常大的优化。您通过值而不是通过引用传递所有向量。这意味着每个函数调用都在复制非常低效的向量。

    由于 java 中的所有内容都是指针,因此您的 java 代码不会制作所有这些副本,这应该是 C++ 代码减速的主要部分。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-08
      • 1970-01-01
      • 2010-12-09
      • 1970-01-01
      • 2016-12-20
      • 2012-10-03
      • 2021-04-16
      • 1970-01-01
      相关资源
      最近更新 更多