Why is A / <constant-int> faster when A is unsigned and signed?

I read C ++ Optimization wikibook . In the faster operation section, one of the tips is as follows:

Integer division by constant

When you divide an integer (which is known to be positive or zero), the constant is to convert the integer to unsigned.

If s is a signed integer, u is an unsigned integer, and C is a constant integer expression (positive or negative), the s / C operation is slower than u / C, and s% C is slower than u% C. This most when C is the strength of two, but in all cases the sign should be taken into account in the separation.

Converting from signed to unsigned, however, is free, as it is only a reinterpretation of the same bits. Therefore, if s is a signed integer, which, as you know, is positive or zero, you can speed up its separation using the following (equivalent) expressions: (unsigned) s / C and (unsigned) s% C.

I tested this statement with gcc and the expression u / Cseems to work consistently better thans / c

The following example is also given below:

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

using namespace std;

int main(int argc, char *argv[])
{

    constexpr int vsize = 1e6;
    std::vector<int> x(vsize);
    std::iota(std::begin(x), std::end(x), 0); //0 is the starting number

    constexpr int a = 5;

  auto start_signed = std::chrono::system_clock::now();
  int sum_signed = 0;
    for ([[gnu::unused]] auto  i : x)
    {
        // signed is by default
        int v = rand() % 30 + 1985;   // v in the range 1985-2014

        sum_signed += v / a;
    }
  auto end_signed = std::chrono::system_clock::now();

  auto start_unsigned = std::chrono::system_clock::now();
  int sum_unsigned = 0;
    for ([[gnu::unused]] auto  i : x)
    {
        int v = rand() % 30 + 1985;   // v in the range 1985-2014
        sum_unsigned += static_cast<unsigned int>(v) / a;
    }
  auto end_unsigned = std::chrono::system_clock::now();

  // signed
  std::chrono::duration<double> diff_signed = end_signed - start_signed;
  std::cout << "sum_signed: " << sum_signed << std::endl;
  std::cout << "Time it took SIGNED: " << diff_signed.count() * 1000 << "ms" << std::endl;

  // unsigned
  std::chrono::duration<double> diff_unsigned = end_unsigned - start_unsigned;
  std::cout << "sum_unsigned: " << sum_unsigned << std::endl;
  std::cout << "Time it took UNSIGNED: " << diff_unsigned.count() * 1000 << "ms" << std::endl;

  return 0;
}

You can compile and run the example here: http://cpp.sh/8kie3

Why is this happening?

+4
source share
1 answer

, , , , , ++ 11. , , ( Godbolt).

constexpr int c = 2;

int signed_div(int in){
    return in/c;
}

int unsigned_div(unsigned in){
    return in/c;
}

:

signed_div(int):
  mov eax, edi
  shr eax, 31
  add eax, edi
  sar eax
  ret

unsigned_div(unsigned int):
  mov eax, edi
  shr eax
  ret

? shr eax, 31 ( 31) , , , eax == 0, eax == 1. eax. , " , 1 . ( ).

  • , 1, . .

  • , 1 . , , , , , . , , .

, , , .

--2 . , ( Godbolt).

constexpr int c = 3;

int signed_div(int in){
    return in/c;
}

int unsigned_div(unsigned in){
    return in/c;
}

:

signed_div(int):
  mov eax, edi
  mov edx, 1431655766
  sar edi, 31
  imul edx
  mov eax, edx
  sub eax, edi
  ret
unsigned_div(unsigned int):
  mov eax, edi
  mov edx, -1431655765
  mul edx
  mov eax, edx
  shr eax
  ret

, . , mul imul ( , , , - , , ), , .


  • Godbot x86-64 GCC 7.3 -O2.

  • ++ 11. , cppreference.

+5

Source: https://habr.com/ru/post/1695310/


All Articles