0

Assume a number has very large value such as 9,046,744,073,709,551,615.

I used addition, multiplication and shift left to double the value.

#include <iostream>
using namespace std;

int main(){
   unsigned long long int value = 9046744073709551615;

   cout << (value + value) << endl;
   cout << (value * 2) << endl;  
   cout << (value << 1) << endl;

   return 0;
}

Which one will calculate faster?

Huy
  • 191
  • 6
  • 18
  • 6
    Why don't you examine the assembly output by your compiler with optimizations enabled? You will likely discover that each one translates to identical code. – paddy Mar 22 '19 at 06:14
  • 6
    Compilers are much better at micro-optimisations than humans. Use the one that makes sense in the context of the code. (In this particular case, any decent compiler will perform each operation at compile-time and print a constant.) – molbdnilo Mar 22 '19 at 06:16
  • 3
    Why do you think the magnitude of the number matters? – molbdnilo Mar 22 '19 at 06:21
  • 1
    The optimized assembly for each case is the same when the [value is hardcoded](https://godbolt.org/z/-swH2f) or [read at runtime](https://godbolt.org/z/Fw8zvN). – Blastfurnace Mar 22 '19 at 06:21
  • 1
    Steve Maguire, "Writing Solid Code", 1993: "Over the years, I have tracked down bugs in which programmers used shifts to divide signed values that weren't guaranteed to be positive. I've tracked down bugs in which programmers shifted in the wrong direction. I've even tracked down bugs in which programmers introduced precedence errors by carelessly converting expressions such a=b+c/4 to a=b+c>>2. I don't recall ever tracking down a bug in which a programmer meant to divide by 4 and made a mistake typing the characters / and 4" – david Mar 22 '19 at 07:32

2 Answers2

1

The short answer is none of the alternatives are faster. They produce identical code when compiler optimizations are enabled.

For the case when the value is known at compile time the assembly is this: Compiler Explorer link

movabs  rsi, -353255926290448386
call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<unsigned long long>(unsigned long long)

It loads a constant into a register and calls operator<<(). All three alternatives produce this optimized assembly.

For the case when the value is read at runtime the assembly is this: Compiler Explorer link

mov     rax, QWORD PTR [rsp+8]
mov     edi, OFFSET FLAT:_ZSt4cout
lea     rsi, [rax+rax]
call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<unsigned long long>(unsigned long long)

It uses addition (rax+rax) and calls operator<<(). Again all three alternatives produce the same assembly.

Blastfurnace
  • 18,411
  • 56
  • 55
  • 70
0

I just picked one compiler from godbolt. Indeed there is totally no difference. Play around with the compilers and optimization levels.

With x86-64_gcc in -O0 it results in 3 times this:

    mov     rax, QWORD PTR [rbp-8]
    add     rax, rax
    mov     rsi, rax
    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned long long)
    mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    mov     rdi, rax
    call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
skratchi.at
  • 1,151
  • 7
  • 22