是否可以使用通用方法来减少算法执行的平均工作量?

时间:2015-09-22 19:57:20

标签: c++ algorithm stl

我遇到过(和编写过的)代码,其中布尔关系表达式中标准模板库的自然使用会导致(可能)浪费精力。

例如,

if (std::distance(begin, end) <= 2) { ... }

或者:

if (std::count(begin,end,val) >= 3) { ... }

在这两种情况下,可以编写自定义算法,以避免在部分评估范围后知道答案时进行不必要的迭代/评估。

是否有一种通用方法可用于防止在这些情况下浪费精力?

编辑:尝试解决“近距离”投票问题。

例如,我可以实施bool distance_at_least(begin, end, 3) bool distance_at_most(begin, end, 2) bool count_at_least(begin, end, val, 5)等。

我要求使用单一(通用)方法,可以用于所有这些类型的查询。

编辑:以下是一个变体解决方案的模型,它试图说明为什么我不想写很多变种。

#include <vector>
#include <list>

namespace DETAIL {

    template <class ITER, class CAT>
    bool distance_at_least_dispatch(ITER begin, ITER end, typename std::iterator_traits<ITER>::difference_type n, CAT)
    {
        while (begin != end && n > 0) {
            ++begin;
            --n;
        }
        return n == 0;
    }

    template <class ITER>
    bool distance_at_least_dispatch(ITER begin, ITER end, typename std::iterator_traits<ITER>::difference_type n, std::random_access_iterator_tag)
    {
        return std::distance(begin, end) >= n;
    }
}

template <class ITER>
bool distance_at_least(ITER begin, ITER end, typename std::iterator_traits<ITER>::difference_type n)
{
    using CAT = typename std::iterator_traits<ITER>::iterator_category;
    return DETAIL::distance_at_least_dispatch(begin, end, n, CAT());
}

int main(int argv, char* argc[])
{
    std::vector<int> v;
    std::list<int> l;
    std::generate_n(std::back_inserter(v), 5, std::rand);
    std::generate_n(std::back_inserter(l), 5, std::rand);

    std::cout << distance_at_least(v.begin(), v.end(), 3) << std::endl;
    std::cout << distance_at_least(v.begin(), v.end(), 5) << std::endl;
    std::cout << distance_at_least(v.begin(), v.end(), 6) << std::endl;
    std::cout << distance_at_least(l.begin(), l.end(), 3) << std::endl;
    std::cout << distance_at_least(l.begin(), l.end(), 5) << std::endl;
    std::cout << distance_at_least(l.begin(), l.end(), 6) << std::endl;

    return 0;
}

1 个答案:

答案 0 :(得分:0)

我认为这里的主要问题应该是std::count(b,e,v) < 2对于体面的编译器是否那么糟糕。它是一个模板函数,因此在调用时可以使用完整的实现,这是一个相当简单的函数。这使其成为内联的主要候选者。这意味着优化器将看到计数返回值与<2进行比较,然后是分支。还可以看出,计数一次增加1,并且没有副作用。

因此,可以重新调整代码,移动分支,并消除循环的冗余部分。

在伪代码中,未经优化:

count = 0
:loop
if (*b==v) ++count
++b
if(b!=e) goto loop
if(count >=2) goto past_if
// Then-block
:past_if

现在已经过优化

count = 0
:loop
if (*b==v) ++count
if(count>=2) goto past_if
++b
if(b!=e) goto loop
// Then-block
:past_if

如您所见,这是一个简单的重新排序。只有一条线移动。这不是火箭科学,一个不错的优化者应该能够解决这个问题。