[C++] Differenza di prestazioni con implementazioni simili
Salve, come da titolo vorrei capire come mai i due blocchi nel main() richiedano tempi così elevati e diversi tra di loro per essere eseguiti:
Output compilando con l'opzione [inline]-std=c++14[/inline]:
Output compilando con l'opzione [inline]-std=c++17[/inline]:
In pratica, tralasciando i diversi approcci e le varie ottimizzazioni possibili, e tenendo conto del fatto che le versioni string e vector sono praticamente equivalenti dal punto di vista algoritmico, mi chiedo come mai le prestazioni tra le due versioni siano così diverse tra loro e come mai la situazione cambia ulteriormente al variare dello standard.
Se può essere utile utilizzo codeblocks 20.03 MinGW (GCC 8.1.0).
#include <iostream>
#include <cstdint>
#include <chrono>
#include <string>
#include <vector>
using namespace std;
using namespace std::chrono;
string addition1(const string &s1, const string &s2)
{
string s3;
unsigned int n = 0;
for(unsigned int i = 0; i < s1.size() || i < s2.size(); ++i)
{
n /= 10;
if(i < s1.size())
{
n += s1[i] - '0';
}
if(i < s2.size())
{
n += s2[i] - '0';
}
s3.push_back(n % 10 + '0');
}
if(n >= 10)
{
s3.push_back('1');
}
return s3;
}
string multiplication1(const string &s1, const string &s2)
{
string s3;
string temp;
for(unsigned int i = 0; i < s1.size(); ++i)
{
temp.resize(0);
unsigned int n = 0;
for(unsigned int j = 0; j < i; ++j)
{
temp.push_back('0');
}
for(unsigned int j = 0; j < s2.size(); ++j)
{
n = (s1[i] - '0') * (s2[j] - '0') + n / 10;
temp.push_back(n % 10 + '0');
}
if(n >= 10)
{
temp.push_back(n / 10 + '0');
}
s3 = addition1(s3, temp);
}
return s3;
}
vector<uint8_t> addition2(const vector<uint8_t> &v1, const vector<uint8_t> &v2)
{
vector<uint8_t> v3;
uint8_t n = 0;
for(unsigned int i = 0; i < v1.size() || i < v2.size(); ++i)
{
n /= 10;
if(i < v1.size())
{
n += v1[i];
}
if(i < v2.size())
{
n += v2[i];
}
v3.push_back(n % 10);
}
if(n >= 10)
{
v3.push_back(1);
}
return v3;
}
vector<uint8_t> multiplication2(const vector<uint8_t> &v1, const vector<uint8_t> &v2)
{
vector<uint8_t> v3;
vector<uint8_t> temp;
for(unsigned int i = 0; i < v1.size(); ++i)
{
temp.resize(0);
uint8_t n = 0;
for(unsigned int j = 0; j < i; ++j)
{
temp.push_back(0);
}
for(unsigned int j = 0; j < v2.size(); ++j)
{
n = v1[i] * v2[j] + n / 10;
temp.push_back(n % 10);
}
if(n >= 10)
{
temp.push_back(n / 10);
}
v3 = addition2(v3, temp);
}
return v3;
}
int main()
{
cout << "calcolo di 12345^1000 (versione string): ";
string a = "54321";
string b = "1";
auto start1 = high_resolution_clock::now();
for(unsigned int i = 0; i < 1000; ++i)
{
b = multiplication1(a, b);
}
auto stop1 = high_resolution_clock::now();
auto duration1 = duration_cast<milliseconds>(stop1 - start1);
cout << duration1.count() << " ms" << endl;
//-----------------------------------------------------------
cout << "calcolo di 12345^1000 (versione vector): ";
vector<uint8_t> c = {5, 4, 3, 2, 1};
vector<uint8_t> d = {1};
auto start2 = high_resolution_clock::now();
for(unsigned int i = 0; i < 1000; ++i)
{
d = multiplication2(c, d);
}
auto stop2 = high_resolution_clock::now();
auto duration2 = duration_cast<milliseconds>(stop2 - start2);
cout << duration2.count() << " ms" << endl;
}Output compilando con l'opzione [inline]-std=c++14[/inline]:
calcolo di 12345^1000 (versione string): 366 ms calcolo di 12345^1000 (versione vector): 1090 ms Process returned 0 (0x0) execution time : 1.474 s Press any key to continue.
Output compilando con l'opzione [inline]-std=c++17[/inline]:
calcolo di 12345^1000 (versione string): 1409 ms calcolo di 12345^1000 (versione vector): 1370 ms Process returned 0 (0x0) execution time : 2.796 s Press any key to continue.
In pratica, tralasciando i diversi approcci e le varie ottimizzazioni possibili, e tenendo conto del fatto che le versioni string e vector sono praticamente equivalenti dal punto di vista algoritmico, mi chiedo come mai le prestazioni tra le due versioni siano così diverse tra loro e come mai la situazione cambia ulteriormente al variare dello standard.
Se può essere utile utilizzo codeblocks 20.03 MinGW (GCC 8.1.0).
Risposte
Questo thread sta diventando molto lungo e credo sia opportuno fare due piccoli commenti:
1. Quando si inizia a lavorare su algoritmi più o meno complicati una comprensione (almeno di base) sulla complessità computazionali è molto utile.
2. In questo forum non siamo certamente esperti di questo genere di algoritmi, ma esistono diversi articoli e qualche libro che descrive algoritmi per questo genere di operazioni. Un punto di partenza potrebbe essere quello di guardare una libreria come GMP che descrive che algoritmi usano: https://gmplib.org/manual/Algorithms.
3. Siccome l'implementazione ottimale per determinati processori non è necessariamente la stessa per altri, libreria come GMP fanno uso di implementazioni diverse in base al sistema.
1. Quando si inizia a lavorare su algoritmi più o meno complicati una comprensione (almeno di base) sulla complessità computazionali è molto utile.
2. In questo forum non siamo certamente esperti di questo genere di algoritmi, ma esistono diversi articoli e qualche libro che descrive algoritmi per questo genere di operazioni. Un punto di partenza potrebbe essere quello di guardare una libreria come GMP che descrive che algoritmi usano: https://gmplib.org/manual/Algorithms.
3. Siccome l'implementazione ottimale per determinati processori non è necessariamente la stessa per altri, libreria come GMP fanno uso di implementazioni diverse in base al sistema.
"apatriarca":
Un punto di partenza potrebbe essere quello di guardare una libreria come GMP che descrive che algoritmi usano: https://gmplib.org/manual/Algorithms.
Ottimo, appena possibile gli darò un'occhiata.
Grazie comunque per i vari consigli che mi avete dato!