【问题标题】:Fast generation of number combinations for variable nested for loops快速生成变量嵌套 for 循环的数字组合
【发布时间】:2016-05-20 19:20:50
【问题描述】:

我有以下代码,它为嵌套 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?

【问题讨论】:

  • 你有多少个嵌套循环?
  • 嵌套循环的数量没有限制。

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


【解决方案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

【讨论】:

  • 递归模板似乎是个好主意。并且还证明了为什么 C++ 是一门糟糕的语言。但这是个好主意。
  • @zmbq 不要怪语言,要怪程序员。 OP 表明他的临时模板解决方案生成的代码比更简单的嵌套 for 循环更差。这是谁的错?
  • @Matzi 您的解决方案确实比我上面发布的要慢得多,更不用说嵌套版本本身了。您的解决方案的真正问题是您使用了两个 divisions,这会消耗太多 CPU 周期
  • @TasamFarkie 我没有对它进行基准测试,但是有两个部门应该不是问题。但是,如果您想真正高效,可以尝试编写递归模板,该模板可以生成与嵌套循环完全相同的等价物。
  • @TasamFarkie 你是对的。请参阅上面的编辑答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-25
  • 1970-01-01
  • 1970-01-01
  • 2021-09-15
  • 1970-01-01
  • 2017-10-05
相关资源
最近更新 更多