为变量嵌套for循环快速生成数字组合

时间:2016-05-20 19:20:51

标签: c++ performance loops c++11

我有以下代码,它为变量嵌套for循环

生成数字/索引的组合
#include <iostream>
#include <array>

template<size_t ... Rest>
inline void index_generator() {
    constexpr int size = sizeof...(Rest);
    std::array<int,size> maxes = {Rest...};
    std::array<int,size> a;
    int i,j;
    std::fill(a.begin(),a.end(),0);

    while(1)
    { 
        for(i = 0; i<size; i++) {
            std::cout << a[i] << " ";
        }
        std::cout << "\n";

        for(j = size-1 ; j>=0 ; j--)
        {
            if(++a[j]<maxes[j])
                break;
            else
                a[j]=0;
        }
        if(j<0)
            break;
    }
}

int main()
{
    index_generator<2,3,3>();
    return 0;
}

输出以下

0 0 0 
0 0 1 
0 0 2 
0 1 0 
0 1 1 
0 1 2 
0 2 0 
0 2 1 
0 2 2 
1 0 0 
1 0 1 
1 0 2 
1 1 0 
1 1 1 
1 1 2 
1 2 0 
1 2 1 
1 2 2

这确实等同于

for (int i=0; i<2; ++i)
    for (int j=0; j<3; ++j)
        for (int k=0; i<3; ++k)

我可以使用上面的方法生成任意数量的nested for loops,但是我注意到随着循环次数的增加,这个代码的执行速度越来越慢,与其等价的对应物相比(即嵌套for循环) )。我已使用gcc 5.3clang 3.8进行了检查。也许这是因为处理器很难预测while(true)中的分支或者其他东西。

我在最里面的循环中所做的通常是从两个数组中访问数据并对它们进行乘法运算,如c_ptr[idx] +=a_ptr[idx]*b_ptr[idx]。由于使用嵌套for循环和使用上述技术生成的索引是相同的,因此内存访问模式保持不变。所以我很确定就数据访问而言,这不是缓存未命中/命中问题。

所以我的问题是:

  1. 有没有办法像嵌套的for循环样式代码那样快速地生成这些组合/索引,或者甚至可能更快?
  2. 由于我们知道要设置的for循环的数量以及for循环的索引在编译时是已知的,是否可以利用更好的优化机会?例如SIMD?

1 个答案:

答案 0 :(得分:1)

您可以使用所有维度的乘法的单个循环生成它,并使用模数作为最终索引。

#include <iostream>
#include <array>

template<size_t ... Rest>
inline void index_generator( ) {
    constexpr int size = sizeof...( Rest );
    std::array<int, size> maxes = { Rest... };
    int total = 1;
    for (int i = 0; i<size; ++i) {
        total *= maxes[i];
    }

    for (int i = 0; i < total; ++i) {
        int remaining = total;
        for (int n = 0; n < size; ++n) {
            remaining /= maxes[n];
            std::cout << ( i / remaining ) % maxes[n] << " ";
        }
        std::cout << std::endl;
    }
}

或者只生成递归模板以实际生成嵌套循环并让编译器为您优化它。这取决于指数的实际用途。现在你的功能不太有用。

编辑:

对三个解决方案进行基准测试,首先是问题中的一个,第二个是没有数组的我的,而他们是递归模板。最后一个有一个错误,它有点难以访问要使用的实际参数,但并非不可能。还必须添加一个总和计算,以免被优化出来,并且必须删除控制台输出以减少基准测试中的影响。结果来自我的i7机器发布模式(VS 2015社区)以及下面给出的设置。 LOGPROFILE_SCOPE是我的宏。

#include <array>

// Original from the question
template<size_t ... Rest>
inline void index_generator1( ) { 
    constexpr int size = sizeof...( Rest );
    std::array<int, size> maxes = { Rest... };
    std::array<int, size> a;
    int i, j;
    std::fill( a.begin( ), a.end( ), 0 );

    int x = 0;

    while (1) {
        for (i = 0; i < size; i++) {
            x += a[i];
        }

        for (j = size - 1; j >= 0; j--) {
            if (++a[j] < maxes[j])
                break;
            else
                a[j] = 0;
        }
        if (j < 0)
            break;
    }

    LOG( x )
}

// Initial try
template<size_t ... Rest>
inline void index_generator2( ) { 
    constexpr int size = sizeof...( Rest );

    int x = 0;

    std::array<int, size> maxes = { Rest... };
    int total = 1;
    for (int i = 0; i < size; ++i) {
        total *= maxes[i];
    }

    for (int i = 0; i < total; ++i) {
        int remaining = total;
        for (int n = 0; n < size; ++n) {
            remaining /= maxes[n];
            x += ( i / remaining ) % maxes[n];
        }
    }

    LOG(x)
}


// Recursive templates
template <int... Args>
struct Impl;

template <int First, int... Args>
struct Impl<First, Args...>
{
    static int Do( int sum )
    {
        int x = 0;
        for (int i = 0; i < First; ++i) {
            x += Impl<Args...>::Do( sum + i );
        }

        return x;
    }
};

template <>
struct Impl<>
{
    static int Do( int sum )
    {
        return sum;
    }
};

template <int... Args>
void index_generator3( )
{
    LOG( Impl<Args...>::Do( 0 ) );
}

执行代码

{
    PROFILE_SCOPE( Index1 )
    index_generator1<200, 3, 400, 20>( );
}
{
    PROFILE_SCOPE( Index2 )
    index_generator2<200, 3, 400, 20>( );
}
{
    PROFILE_SCOPE( Index3 )
    index_generator3<200, 3, 400, 20>( );
}

控制台中的结果:

[19:35:50]: 1485600000
[19:35:50]: 1485600000
[19:35:50]: 1485600000

[19:35:56]:          PerCall(ms)
[19:35:56]:   Index1     10.4016
[19:35:56]:   Index2     75.3770
[19:35:56]:   Index3      4.2299