int vs short

0

Potrzebny jest mi niewielki zakres liczb. Wypada uzyc typu short lub nawet signed lub unsigned char dla oszczedzenia pamieci.
Jednak liczby te sa bardzo czesto uzywane. Jako ze procesor (chyba ?) najszybciej liczy na typie int to lepiej bedzie dla szybkosci dzialania programu zastosowac int ?

8

Napisz test, który zmierzy, która wersja działa szybciej i jaka będzie różnica w zużyciu pamięci.
W każdym innym wypadku będzie to wróżenie z fusów.

3

Możesz też rozważyć typ z rodziny fast, np. uint_fast16_t, one powinny być optymalizowane pod szybkość na architekturze docelowej, ale pomiaru to nie zastąpi.

1

signed lub unsigned to ten sam zakres pamieci

0

Jestem zaskoczony. Po napisaniu testu wychodzi ze u mnie na shorcie wykona sie szybciej...
Ale po napisaniu dla innych zakresow wartosci (iteracje 0-10, 0-20) to int jest szybszy, a 0-5000 to short

0

Biorąc pod uwagę współczesne procesory x86 to dzielenie na 16-bitowych liczbach powinno być szybsze, ale mnożenie, dodawanie, odejmowanie i operacje bitowe powinny być szybsze na liczbach 32-bitowych.

Tak przynajmniej obstawiam :]

PS. jest jeszcze jedna kwestia - 16-bitowe liczby zajmują 2x mniej pamięci, a więc w pamięci podręcznej procesora zmieści się 2x więcej liczb 16-bitowych co 32-bitowych. To może mieć wpływ na wydajność jeśli masz sporo losowego dostępu do pamięci.

PPS. bardzo proste pętle kompilator może skutecznie zwektoryzować. Maksymalny rozmiar wektora na danym procesorze jest generalnie stały niezależnie od typów składowych, więc przy 16-bitowych składowych będzie ich 2x więcej niż przy 32-bitowych składowych i operacje będą prawdopodobnie znacznie (do 2x) szybsze (zakładając, że przepustowość RAMu nas nie ogranicza). Sprawdź jak wygląda kod po dezasemblacji, np. tutaj: https://godbolt.org/

alagner napisał(a):

Możesz też rozważyć typ z rodziny fast, np. uint_fast16_t, one powinny być optymalizowane pod szybkość na architekturze docelowej, ale pomiaru to nie zastąpi.

To _fast raczej dotyczy operacji arytmetycznych, a nie dostępu do pamięci, więc powinno być stosowane raczej do zmiennych lokalnych.

4
teofrast napisał(a):

Jestem zaskoczony. Po napisaniu testu wychodzi ze u mnie na shorcie wykona sie szybciej...

Ale po napisaniu dla innych zakresow wartosci (iteracje 0-10, 0-20) to int jest szybszy, a 0-5000 to short

Pewnie wąskim gardłem jest dostęp do pamięci.
Trudno powiedzieć co dokładnie bo nie pokazałeś nawet jednej linijki kodu.
Na dodatek, zgaduje, że zapomniałeś włączyć optymalizacje (większość początkujących o tym nie wie, że trzeba).

Do tego jeszcze dochodzi fakt, że napisanie dobrego testu performance nie jest tak łatwe jak się wydaje, bo kompilator jest za mądry podczas optymalizowania.

1

A jak dużo jest tych danych? I jak często robisz na nich operacje?

1

Testowy program:

#include <cassert>
#include <string>
#include <cstdint>
#include <climits>
#include <iostream>

using namespace std;

#include <chrono>

using namespace chrono;
/*
template<class Type>
inline void duration_test(const Type& FIRST, const Type& END, const P_Funtion p) {
   cout << "FIRST = " << FIRST << '\n';
   cout << "END = " << END << '\n';
   cout << "Duration + test for: " << typeid(Type).name();
   const time_point<high_resolution_clock> t1 = high_resolution_clock::now();
   for (Type i = FIRST; i < END; )
      i++;
   const time_point<high_resolution_clock> t2 = high_resolution_clock::now();
   const nanoseconds ns = duration<long, nano>(t2 - t1);  
   cout << "   =   " << ns.count() << '\n';
}
*/
template<class Type>
inline void add(const Type FIRST, const Type END) {
   //printf ("__FUNCTION__ name = %s\n", __FUNCTION__);
   //cout << "\n " << __FUNCTION__ << '\n';
   for (Type i = FIRST; i < END; )
      i++;
}

template<class Type>
inline void sub(const Type FIRST, const Type END) {
   for (Type i = FIRST; i < END; )
      i--;
}

//typedef void (*F_P)(Type &, Type & ); 
template<class Type, typename F_P>
inline void duration_test(const Type& FIRST, const Type& END, F_P fp) {
   cout << "FIRST = " << FIRST << '\n';
   cout << "END = " << END << '\n';
   cout << "Duration test for: " << typeid(Type).name();
   //cout << "\n " << fp << '\n';
   const time_point<high_resolution_clock> t1 = high_resolution_clock::now();
   fp(FIRST, END);
   const time_point<high_resolution_clock> t2 = high_resolution_clock::now();
   const nanoseconds ns = duration<long, nano>(t2 - t1);  
   cout << "   =   " << ns.count() << '\n';
}

int main() {
   /*
   constexpr long FIRST = -10L; 
   constexpr long END = +11L;
   duration_test(FIRST, END);
   duration_test(short(FIRST), short(END));
   duration_test(int(FIRST), int(END));
   duration_test(char(FIRST), char(END));
   */
   constexpr long FIRST = -10L; 
   constexpr long END = +11L;
   void (* add_long)(long, long) = add;
   duration_test(FIRST, END, add_long);
   
   void (* add_short)(short, short) = add;
   duration_test(short(FIRST), short(END), add_short);
   
   void (* add_int)(int, int) = add;
   duration_test(int(FIRST), int(END), add_int);
   
   void (* add_char)(char, char) = add;
   duration_test(char(FIRST), char(END), add_char);
   
   add_long = sub;
   duration_test(END, FIRST, add_long);
   
   add_short = sub;
   duration_test(short(END), short(FIRST), add_short);
   
   add_int = sub;
   duration_test(int(END), int(FIRST), add_int);
   
   add_char = sub;
   duration_test(char(END), char(FIRST), add_char);
   
}

// g++ -Wall -std=c++14 -fcompare-debug-second -o integer_test integer_test.cpp

5
teofrast napisał(a):

Testowy program:

// g++ -Wall -std=c++14 -fcompare-debug-second -o integer_test integer_test.cpp

Chyba nie włączyłeś żadnych optymalizacji, przez co test jest jeszcze bardziej nierealistyczny niż ustawa przewiduje. Dodaj -O2 albo -O3 do opcji kompilacji.

4

Jest jeszcze gorzej, bo te funkcje (szablony):

template<class Type>
inline void add(const Type FIRST, const Type END) {
   //printf ("__FUNCTION__ name = %s\n", __FUNCTION__);
   //cout << "\n " << __FUNCTION__ << '\n';
   for (Type i = FIRST; i < END; )
      i++;
}

template<class Type>
inline void sub(const Type FIRST, const Type END) {
   for (Type i = FIRST; i < END; )
      i--;
}

po włączeniu optymalizacji zostaną po prostu usunięte, bo kompilator zauważy, że ten kod nie ma żadnych widocznych skutków.

Dobrze to widać tutaj: https://godbolt.org/z/hb1joE39h

void add<long>(long, long):
        ret
void add<short>(short, short):
        ret
void add<int>(int, int):
        ret
void add<char>(char, char):
        ret
void sub<long>(long, long):
        ret
void sub<short>(short, short):
        ret
void sub<int>(int, int):
        ret
void sub<char>(char, char):
        ret

Czyli wygenerowane funkcje są puste.

Tak jak ostrzegałem:

MarekR22 napisał(a):

Do tego jeszcze dochodzi fakt, że napisanie dobrego testu performance nie jest tak łatwe jak się wydaje, bo kompilator jest za mądry podczas optymalizowania.

0

Jak teraz patrze na kod ktory wstawilem to musze go poprawic, przede mna jeszcze bardzo wiele nauki, ale to bardzo dobrze, bo nie bede sie nudzil. Zadajac proste pytanie nie spodziewalem sie ze wypadki potocza sie tak jak to teraz widze - wrecz mnie zamurowalo ...

#include <cassert>
#include <string>
#include <cstdint>
#include <climits>
#include <iostream>
#include <cmath>
#include <vector>

using namespace std;

#include <chrono>
using namespace chrono;

template<class Type>
inline Type sum(const Type& X, const Type& Y) {
   //cout << "\n " << __func__ << '\n';
   long long diff = X - Y;
   if (0 == diff)
      throw invalid_argument("difference can not be 0");
   const Type FIRST = diff < 0 ? X : Y;
   const Type END   = diff > 0 ? X : Y;
   //cout << "FIRST = " << FIRST << '\n';
   //cout << "END   = " << END << '\n';
   Type result = 0;
   for (Type value = FIRST; value < END; value++)
      result += value;
   //cout << " Function sum for " << typeid(Type).name() << " = " << result;
   return result;
}

template<class Type>
inline void add(const Type& X, const Type& Y) {
   //printf ("__FUNCTION__ name = %s\n", __FUNCTION__);
   vector<Type> vec(llabs(X - Y));
   cout << "\n" << __func__ << "() size of vector type " << typeid(Type).name() << " = " << vec.size() << '\n';
   for (Type index = 0; index < vec.size(); index++)
      try {
         vec.at(index) = sum(X, Y);    
      }
      catch (const out_of_range& e) {
         cerr << "Out of Range error: " << e.what() << '\n';
      }

   for (Type index = 1; index < vec.size(); index++) 
      vec[index] += vec[index - 1];
}

template<class Type>
inline Type difference(const Type& X, const Type& Y) {
   //cout << "\n " << __func__ << '\n';
   long long diff = X - Y;
   if (0 == diff)
      throw invalid_argument("difference can not be 0");
   const Type FIRST = diff > 0 ? X : Y;
   const Type END   = diff < 0 ? X : Y;
   //cout << "FIRST = " << FIRST << '\n';
   //cout << "END   = " << END << '\n';
   Type result = 0;
   for (Type value = FIRST; value > END; value--)
      result -= value;
   //cout << " Function difference for " << typeid(Type).name() << " = " << result;
   return result;
}

template<class Type>
inline void sub(const Type& X, const Type& Y) {
   vector<Type> vec(llabs(X - Y));
   cout << "\n" << __func__ << "() size of vector type " << typeid(Type).name() << " = " << vec.size() << '\n';
   for (Type index = 0; index < vec.size(); index++)
      try {
         vec.at(index) = difference(X, Y);    
      }
      catch (const out_of_range& e) {
         cerr << "Out of Range error: " << e.what() << '\n';
      }

   for (Type index = 1; index < vec.size(); index++) 
      vec[index] -= vec[index - 1];
}
 
template<class Type, typename F_P>
inline void duration_test(const Type& FIRST, const Type& END, F_P fp) {
   if (nullptr == fp)
      throw invalid_argument("pointer to function can not be NULL");
   static constexpr int MIN_DIFF = 21;
   if (labs(FIRST - END) < MIN_DIFF)
      throw invalid_argument("Min difference between numeric arguments is " + to_string(MIN_DIFF));
   cout << "\n\nFIRST = " << FIRST << '\n';
   cout << "END   = " << END << '\n';
   const string TYPE_NAME       = typeid(Type).name();
   const string P_FUNCTION_NAME = typeid(F_P).name();
   cout << "Duration test for type = " << TYPE_NAME;
   cout << " and pointer to function = " << P_FUNCTION_NAME << '\n';
   if (P_FUNCTION_NAME.find(TYPE_NAME) == string::npos)
      throw invalid_argument("pointer to function is different than argument type ");
   
   const time_point<high_resolution_clock> t1 = high_resolution_clock::now();
   fp(FIRST, END);
   const time_point<high_resolution_clock> t2 = high_resolution_clock::now();
   const nanoseconds ns = duration<long, nano>(t2 - t1);  
   cout << "Duration test for type = " << TYPE_NAME << " was counted nanoseconds   =   " << ns.count() << '\n';
}

void duration_test(const long FIRST, const long END) {
   void (* pf_long)(const long&, const long&) = &add;
   duration_test(FIRST, END, pf_long);

   void (* pf_short)(const short&, const short&) = &add;
   duration_test(short(FIRST), short(END), pf_short);

   void (* pf_int)(const int&, const int&) = add;
   duration_test(int(FIRST), int(END), pf_int);

   void (* pf_char)(const char&, const char&) = add;
   duration_test(char(FIRST), char(END), pf_char);

   pf_long = sub;
   duration_test(END, FIRST, pf_long);

   pf_short = sub;
   duration_test(short(END), short(FIRST), pf_short);

   pf_int = sub;
   duration_test(int(END), int(FIRST), pf_int);

   pf_char = sub;
   duration_test(char(END), char(FIRST), pf_char);
}

int main() {
   for (long FIRST = -11, END = -FIRST; END < 30; FIRST -= 10, END = -FIRST)
      duration_test(FIRST, END);
}

// g++ -Wall -O2 -std=c++14 -fcompare-debug-second -o integer_test integer_test.cpp

0

Ostetecznie wybralem short - wypada zadowalajaco pod wzgledem szybkosci i pamieci, z reszta moge w kazdej chwili zmienic typ wektora za pomoca typedef.
Mnozenie, dzielenie i modulo to odpuszcze sobie bo rzedko ich w programie bede uzywac, glownie dodawanie i odejmowanie na cyfrach 0-9.
Ostateczny kod testu:

#include <string>
#include <iostream>
#include <cmath>

using namespace std;

#include <chrono>
using namespace chrono;

typedef long (* long_PF_2cr_long)(const long&, const long&);
typedef short (* short_PF_2cr_short)(const short&, const short&);
typedef int (* int_PF_2cr_int)(const int&, const int&);
typedef char (* char_PF_2cr_char)(const char&, const char&); 

template<class Type>
Type sum(const Type& X, const Type& Y) {
   cout << "\n " << __func__ << ' ';
   long long diff = X - Y;
   if (0 == diff)
      throw invalid_argument("difference can not be 0");
   const Type FIRST = diff < 0 ? X : Y;
   const Type END   = diff > 0 ? X : Y;
   cout << "FIRST = " << FIRST << ' ';
   cout << "END   = " << END << '\n';
   Type result = 0;
   for (Type value = FIRST; value < END; value++)
      result += value;
   return result;
}

template<class Type>
Type difference(const Type& X, const Type& Y) {
   cout << "\n " << __func__ << ' ';
   long long diff = X - Y;
   if (0 == diff)
      throw invalid_argument("difference can not be 0");
   const Type FIRST = diff > 0 ? X : Y;
   const Type END   = diff < 0 ? X : Y;
   cout << "FIRST = " << FIRST << ' ';
   cout << "END   = " << END << '\n';
   Type result = 0;
   for (Type value = FIRST; value > END; value--)
      result -= value;
   return result;
}

template<class Type, typename F_P>
void duration_test(const Type& X, const Type& Y, F_P fp) {
   if (nullptr == fp)
      throw invalid_argument("pointer to function can not be NULL");
   static constexpr int MIN_DIFF = 21;
   if (llabs(X - Y) < MIN_DIFF)
      throw invalid_argument("Min difference between numeric arguments is " + to_string(MIN_DIFF));
   const string TYPE_NAME       = typeid(Type).name();
   const string P_FUNCTION_NAME = typeid(F_P).name();
   cout << "\n\t$$$ START of Duration test for type = " << TYPE_NAME;
   cout << " and pointer to function = " << P_FUNCTION_NAME << '\n';
   if (P_FUNCTION_NAME.find(TYPE_NAME) == string::npos)
      throw invalid_argument("pointer to function is different than argument type ");
   
   const time_point<high_resolution_clock> t1 = high_resolution_clock::now();
   fp(X, Y);
   const time_point<high_resolution_clock> t2 = high_resolution_clock::now();
   const nanoseconds ns = duration<long, nano>(t2 - t1);  
   cout << "\n\t*** RESULT of Duration test for type = " << TYPE_NAME << " was counted nanoseconds   =   " << ns.count() << '\n';
}

struct Function_Pointers {
private:
   long_PF_2cr_long  pf_long    {0};
   short_PF_2cr_short pf_short   {0};
   int_PF_2cr_int   pf_int     {0};
   char_PF_2cr_char  pf_char    {0};
public:
   Function_Pointers(long_PF_2cr_long l, short_PF_2cr_short s, int_PF_2cr_int i, char_PF_2cr_char c) {
      if (!l || !s || !i || !c)
         throw invalid_argument("pointer to function can not be NULL");
      pf_long = l;
      pf_short = s;
      pf_int = i;
      pf_char = c;
   }
   long_PF_2cr_long  plong() const { return pf_long; }
   short_PF_2cr_short pshort() const { return pf_short; }
   int_PF_2cr_int pint() const { return pf_int; }
   char_PF_2cr_char pchar() const { return pf_char; }
};

void duration_test_1(const long X, const long Y, const Function_Pointers * const P_FUNCTIONS) {
   if (! P_FUNCTIONS)
      throw invalid_argument("pointer to struct can not be NULL");
   duration_test(X, Y, P_FUNCTIONS->plong());
   duration_test(short(X), short(Y), P_FUNCTIONS->pshort());
   duration_test(int(X), int(Y), P_FUNCTIONS->pint());
   duration_test(char(X), char(Y), P_FUNCTIONS->pchar());
}

void duration_test_1(const long X, const long Y) {
   const Function_Pointers SUM = Function_Pointers(sum, sum, sum, sum);
   duration_test_1(X, Y, &SUM);
   const Function_Pointers DIFF = Function_Pointers(difference, difference, difference, difference);
   duration_test_1(X, Y, &DIFF);
}

int main() {
   for (long FIRST = -11, END = -FIRST; END < 20; FIRST -= 10, END = -FIRST) {
      duration_test_1(FIRST, END);
      break;
   }
}

// g++ -Wall -Wextra -O2 -std=c++14 -fcompare-debug-second -o integer_test integer_test.cpp


0

Jeszcze jak benchmarkujesz mikro-operacje w rodzaju dodawania, to między pomiarami czasu musi być znacząca liczba operacji (milion lub więcej), inaczej będziesz miał duże błędy pomiaru.
Idealnie jakby każda testowana funkcja popracowała min. 1s na średniej klasy sprzęcie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1