6

I wrote the following code to test the time taken for a constexpr factorial to evaluate vs normal way

#include<iostream>
#include<chrono>

constexpr long int factorialC(long int x){  return x*(x <2?1 : factorialC(x-1));}
using ns = std::chrono::nanoseconds;
using get_time = std::chrono::steady_clock;
void factorial(long int x){
    long int suma=1;
    for(long int i=1; i<=x;i++)
    {
        suma=suma*i;

    }
    std::cout<<suma<<std::endl;
}

int main(){
    long int x = 13;

    std::cout<<"Now calling the constexpr"<<std::endl;
    auto start1 = get_time::now();
    std::cout<<factorialC(x)<<std::endl;
    auto end1 = get_time::now();

    std::cout<<"Now calling the normal"<<std::endl;
    auto start2 = get_time::now();
    factorial(x);
    auto end2 = get_time::now();
    std::cout<<"Elapsed time for constexpr is "<<std::chrono::duration_cast<ns>(end1-start1).count()
    <<" Elapsed time for normal is "<<std::chrono::duration_cast<ns>(end2-start2).count()<<std::endl;
}

When I run the code I am getting

Now calling the constexpr                                                                                                   
1932053504                                                                                                                  
Now calling the normal                                                                                                      
1932053504                                                                                                                  
Elapsed time for constexpr is 81812 Elapsed time for normal is 72428  

But constexpr should take nearly "0" time because it has already been calculated during compilation time .

But surprisingly constexpr calculation takes more time than a normal factorial to work. I have tried to follow this question, but I am not able understand the answer in my context.

Please help me to understand it.

I compiled the code through (filename is constexpr.cpp)

g++ --std=c++11 constexpr.cpp   

V2:-

after @rici input, I did change line number 18 to

const long int x =13;

The results now are

Now calling the constexpr                                                                                                   
1932053504                                                                                                                  
Now calling the normal                                                                                                      
1932053504                                                                                                                  
Elapsed time for constexpr is 114653 Elapsed time for normal is 119052  

It seems once I mentioned the x to be const, the compiler is calculating factorialC at compile time

I am using 4.9.3 version of g++ from MinGW32 on windows

Community
  • 1
  • 1
srinath29
  • 345
  • 2
  • 4
  • 14
  • 3
    Just calling each function once won't give much of a data point. Call it in a loop a few thousand times and print the average. – Some programmer dude Sep 19 '16 at 16:30
  • 5
    Furthermore, the two functions don't really do the same thing! Comparing them is close to useless unless they both do the same thing. – Some programmer dude Sep 19 '16 at 16:30
  • isn't `constexpr` really only guaranteed to be a compile time thing if you call the function with a constant, not a variable? – GreatAndPowerfulOz Sep 19 '16 at 16:36
  • 1
    @Great.And.Powerful.Oz Not really. The compiler is free to call it a compile time if it wants to. But if you use it somewhere where the value has to be known at compile time, it will be evaluated at compile time. – Rakete1111 Sep 19 '16 at 16:37
  • @Rakete1111, ok thanks. But even so, does the compiler ever evaluate it at compile time if the `constexpr` function is called with non-const variable? – GreatAndPowerfulOz Sep 19 '16 at 16:39
  • 1
    @Great.And.Powerful.Oz Oh, I see what you mean. Yes. But the value could also be a literal for example, it just has to be available at compile time. – Rakete1111 Sep 19 '16 at 16:40
  • Could the results that the OP is seeing be skewed by including the time taken to stream the answer thru' `std::cout` ? – quamrana Sep 19 '16 at 16:42
  • 2
    @quamrana: Absolutely. 13 multiplications are a lot less work than converting the result of the 13 multiplications into an ascii string. – rici Sep 19 '16 at 16:47
  • Downvoted for no information about optimization flags passed to compiler before you timed it. – Yakk - Adam Nevraumont Sep 19 '16 at 19:37
  • 1
    Let's worry about correctness before speed. 13! is 6,227,020,800 not 1,932,053,504. – Adrian McCarthy Sep 19 '16 at 20:14
  • @Adrian McCarthy nice catch, 13! overflows the 32-bit long value. – Christopher Oicles Sep 19 '16 at 23:54
  • @AdrianMcCarthy Hi, I didn't worry about correctness of answer because both the functions are returning the same answer and obviously, it is an overflow . – srinath29 Sep 20 '16 at 13:24
  • @rici My assumption was constexpr will evaluate at compile time. **Suppose** the _factorialC_ was calculated at compile time then it should obviously take less time than the _factorial_ because during runtime _factorialC(x)_ is a value and _factorial_ is a function call and both should take constant time to convert to ascii string – srinath29 Sep 20 '16 at 13:35
  • 1
    @srinath29. I think it is being computed at compile-time, provided you are compiling with optimization, but there is no guarantee: the argument is *not* a constant expression (much less a constexpr) and signed integer overflow is UB, which makes the call not constexpr. But my point was that you are not timing the computation; you are timing outputting the value to cout. And compared wurh the cost of formatted output, the computation of 13! is just noise. – rici Sep 20 '16 at 14:24

3 Answers3

10

The problem is that something constexpr is not guaranteed to be evaluated at compile time. The keyword constexpr just says that it can, but the compiler is free to evaluate it at run time too, as it sees fit.

The difference in run time is probably because you 1) aren't doing it enough (one iteration is nothing) and 2) recursion isn't as fast as iteration (I think, although the difference is minimal).

To guarantee compile time evaluation, you will have to use it in a context where the compiler has to evaluate it at compile time, something like a template for example:

template<unsigned long long n>
auto evaluate() { return n; }

//...
auto start1 = get_time::now();
std::cout << evaluate<factorialC(x)>() << std::endl; //factorialC is evaluted
                                                     //at compile timme
auto end1 = get_time::now();

There is also a standard library function for evaluate, std::integral_constant. You can use that instead.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • 2
    See also http://stackoverflow.com/questions/14294271/forcing-a-constant-expression-to-be-evaluated-during-compile-time#answer-14297002 for a nice macro wrapper to force compile-time evaluation (that one based on `std::integral_constant`). – Daniel Schepler Sep 19 '16 at 16:42
  • @DanielSchepler Didn't know about that, interesting thanks :) – Rakete1111 Sep 19 '16 at 16:43
  • @Rakete1111: gcc with optimization enabled does compute the value at compile time, but not without optimization, probably because it needs optimization to figure out that `x` (which is *not* a constant) has not been modified. – rici Sep 19 '16 at 16:48
  • @rici That sounds plausible. Anyway, it is up to the compiler to decide whether it wants to evaluate it at compile or run time :) – Rakete1111 Sep 19 '16 at 16:51
  • 2
    Yup. But it is also reasonable to expect a good compiler to make a good decision. Anyway, i would definitely have made the variable const, and not only for the compiler's benefit. – rici Sep 19 '16 at 19:17
2

long int x = 13; That''s not a constant expression, so the compiler can't evaluate factorial(x); at compile time.

Try to send it constant values, like a constexpr value so it can do the evaluation:

int main(){
    long int x = 13;
    constexpr long y = 13;

    std::cout << "Now calling the constexpr" << std::endl;

    auto start1 = get_time::now();

    // Notice the use of a constexpr value here!
    std::cout << factorialC(y) << std::endl;
    auto end1 = get_time::now();


    std::cout << "Now calling the normal" << std::endl;
    auto start2 = get_time::now();

    // Simply call your function witha runtime value.
    // Try to ensure that the compiler don't inline the obvious value of x
    std::cout << factorialC(x) << std::endl;
    auto end2 = get_time::now();

    std::cout << "Elapsed time for constexpr is "
        << std::chrono::duration_cast<ns>(end1-start1).count()
        << " Elapsed time for normal is "
        << std::chrono::duration_cast<ns>(end2-start2).count()
        << std::endl;
}

By the way, you should compaire apples with apples when talking about performance.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • Thank you for your input, I realized that from @rici answer. After computing with _const_ varibale, it is working as expected. – srinath29 Sep 20 '16 at 13:46
1

Note, that it can not be computed at a compile time, because compiler does not know nothing about a value of a long int x in this function:

 constexpr long int factorialC(long int x)

If you want compile time factorial you can use templates instead. Something like:

 #include <iostream>
 template<int N> inline int factorial(){ return N*factorial<N-1>(); }
 template<> inline int factorial<1>(){ return 1; }

 int main()
 {
     std::cout << factorial<13>() << std::endl;
     return 0;
 }
Alex Chudinov
  • 654
  • 1
  • 6
  • 12
  • Hi, thank you for your valuable time. I wanted to know why constexpr was not behaving as it supposed to be (As per my understanding) so asked about it to improve my understanding – srinath29 Sep 20 '16 at 13:36
  • Hi! Actually I am not clearly understand what does `constexpr` mean in your example. Compiler should know everything about your function to do the `constexpr` from it for you. However, in your case compiler does not know the value of x, because it is not an `constexpr`. Hence, you can try to change `long int x = 13;` to `constexpr long int = 13;`, may be it could help. The same as said below by @Guillaume. – Alex Chudinov Sep 20 '16 at 13:57
  • Thank you. I did the same and could see the changes. I have also edited my question to reflect the same. :) – srinath29 Sep 20 '16 at 14:05