Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loop fusion in C++ (how to help the compiler?)

I try to understand under what circumstances a C++ compiler is able to perform loop fusion and when not.

The following code measures the performance of two different ways to calculate the squared doubles (f(x) = (2*x)^2) of all values in a vector.

#include <chrono>
#include <iostream>
#include <numeric>
#include <vector>

constexpr int square( int x )
{
    return x * x;
}

constexpr int times_two( int x )
{
    return 2 * x;
}

// map ((^2) . (^2)) $ [1,2,3]
int manual_fusion( const std::vector<int>& xs )
{
    std::vector<int> zs;
    zs.reserve( xs.size() );
    for ( int x : xs )
    {
        zs.push_back( square( times_two( x ) ) );
    }
    return zs[0];
}

// map (^2) . map (^2) $ [1,2,3]
int two_loops( const std::vector<int>& xs )
{
    std::vector<int> ys;
    ys.reserve( xs.size() );
    for ( int x : xs )
    {
        ys.push_back( times_two( x ) );
    }

    std::vector<int> zs;
    zs.reserve( ys.size() );
    for ( int y : ys )
    {
        zs.push_back( square( y ) );
    }
    return zs[0];
}

template <typename F>
void test( F f )
{
    const std::vector<int> xs( 100000000, 42 );

    const auto start_time = std::chrono::high_resolution_clock::now();
    const auto result = f( xs );
    const auto end_time = std::chrono::high_resolution_clock::now();

    const auto elapsed = end_time - start_time;
    const auto elapsed_us = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
    std::cout << elapsed_us / 1000 << " ms - " << result << std::endl;
}

int main()
{
    test( manual_fusion );
    test( two_loops );
}

The version with two loops takes about twice as much time as the version with one loop, even with -O3 for GCC and Clang.

Is there a way to allow the compiler to optimize two_loops into being as fast as manual_fusion without operating in-place in the second loop? The reason I'm asking is I want to make chained calls to my library FunctionalPlus like fplus::enumerate(fplus::transform(f, xs)); faster.

like image 241
Tobias Hermann Avatar asked Sep 23 '16 10:09

Tobias Hermann


Video Answer


1 Answers

You can try modify your two_loops function as follows:

int two_loops( const std::vector<int>& xs )
{
    std::vector<int> zs;
    zs.reserve( xs.size() );
    for ( int x : xs )
    {
        zs.push_back( times_two( x ) );
    }

    for ( int i=0 : i<zs.size(); i++ )
    {
        zs[i] = ( square( zs[i] ) );
    }
    return zs[0];
}

The point is to avoid allocating memory twice and push_back to another vector

like image 100
Trifon Avatar answered Oct 15 '22 22:10

Trifon