RISOLTO Efficienza permutazioni C++

Stato
Discussione chiusa ad ulteriori risposte.

M1n021

Nuovo Utente
143
68
Ciao a tutti, sto implementando una classe in C++ finalizzata a ricreare le varie sequenze del calcolo combinatorio: permutazioni (semplici e con ripetizione), combinazioni (semplici e con ripetizione), disposizioni (semplici e con ripetizione) e ripartizioni.
Sembra funzionare tutto, ma, cercando eventuali ottimizzazioni relativamente alle permutazioni, ho notato dei tempi di esecuzione che non riesco a capire. In pratica ho notato che in modalità release utilizzare la funzione sulle permutazioni da me implementata attraverso l'interfaccia di una classe determina un tempo d'esecuzione che è più del doppio rispetto al caso in cui non utilizzo alcuna classe.

Riporto di seguito un codice per spiegare meglio quello che intendo:
C++:
#include <iostream>
#include <algorithm>
#include <chrono>
#include <cstdint>

using namespace std;
using namespace std::chrono;

void scambia_valori(unsigned int &a, unsigned int &b)
{
    unsigned int temp = a;
    a = b;
    b = temp;
}

bool inverti_array(unsigned int *const v, unsigned int dim)
{
    for(unsigned int i_sx = 0, i_dx = dim - 1; i_sx < i_dx; scambia_valori(v[i_sx++], v[i_dx--]));
    return true;
}

class A
{
private:
    unsigned int *u;
    unsigned int k;

public:
    A(const unsigned int N);
    ~A();
    bool permutazione_S_successiva_();
};

A::A(const unsigned int n)
{
    u = new unsigned int[k = n];
    for(unsigned int i = 0; i < k; ++i)
    {
        u[i] = i;
    }
}

A::~A()
{
    delete[] u;
}

inline bool A::permutazione_S_successiva_()
{
    unsigned int i = k;
    while(--i && u[i - 1] > u[i]);
    if(i)
    {
        inverti_array(u + i, k - i);
        for(unsigned int j = i; j < k; ++j)
        {
            if(u[i - 1] < u[j])
            {
                scambia_valori(u[i - 1], u[j]);
                return true;
            }
        }
    }
    return false;
}

bool permutazione_S_successiva_2(unsigned int *u, const unsigned int k)
{
    unsigned int i = k;
    while(--i && u[i - 1] > u[i]);
    if(i)
    {
        inverti_array(u + i, k - i);
        for(unsigned int j = i; j < k; ++j)
        {
            if(u[i - 1] < u[j])
            {
                scambia_valori(u[i - 1], u[j]);
                return true;
            }
        }
    }
    return false;
}

int main()
{
    unsigned int n = 12;
    uint64_t cont;

    //----------------------------------
    auto start1 = high_resolution_clock::now();

    cont = 0;
    A a(n);
    do
    {
        ++cont;
    }
    while(a.permutazione_S_successiva_());

    auto stop1 = high_resolution_clock::now();
    cout << "perm. succ. con classe   " << cont << " " << duration_cast<milliseconds>(stop1 - start1).count() << "ms" << endl;
    //----------------------------------

    //----------------------------------
    auto start2 = high_resolution_clock::now();

    cont = 0;
    unsigned int *u = new unsigned int[n];
    for(unsigned int i = 0; i < n; ++i)
    {
        u[i] = i;
    }
    do
    {
        ++cont;
    }
    while(permutazione_S_successiva_2(u, n));
    delete[] u;

    auto stop2 = high_resolution_clock::now();
    cout << "perm. succ. senza classe " << cont << " " << duration_cast<milliseconds>(stop2 - start2).count() << "ms" << endl;
    //----------------------------------

    //----------------------------------
    auto start3 = high_resolution_clock::now();

    cont = 0;
    unsigned int *v = new unsigned int[n];
    for(unsigned int i = 0; i < n; ++i)
    {
        v[i] = i;
    }
    do
    {
        ++cont;
    }
    while(next_permutation(v, v + n));
    delete[] v;

    auto stop3 = high_resolution_clock::now();
    cout << "std::next_permutation    " << cont << " " << duration_cast<milliseconds>(stop3 - start3).count() << "ms" << endl;
    //----------------------------------
}

Questo l'output compilando senza spuntare alcun flag di ottimizzazione:
Codice:
perm. succ. con classe   479001600 20707ms
perm. succ. senza classe 479001600 19721ms
std::next_permutation    479001600 30232ms

Process returned 0 (0x0)   execution time : 70.720 s
Press any key to continue.

Questo invece quello che ottengo compilando con -O3:
Codice:
perm. succ. con classe   479001600 4777ms
perm. succ. senza classe 479001600 2176ms
std::next_permutation    479001600 2946ms

Process returned 0 (0x0)   execution time : 9.961 s
Press any key to continue.

Come mai la versione che utilizza l'interfaccia di una classe è così tanto più lenta rispetto a quella che invece non ne fa uso?
 
Ultima modifica:

BrutPitt

Utente Attivo
1,166
1,262
Perche' esegui il costruttore (e il ciclo for all'interno del costruttore) all'interno del timing...
Oppss... pero' noto (adesso!) che fai lo stesso anche altrove.... ci guardo meglio ... 😉

Pero' come risultati ottengo questi (con -O3):

Codice:
perm. succ. con classe   479001600 1098ms
perm. succ. senza classe 479001600 1390ms
std::next_permutation    479001600 2722ms

gcc version 12.2.1 20220819 (Red Hat 12.2.1-2) (GCC)

Target: x86_64-redhat-linux
 
Ultima modifica:
  • Mi piace
Reazioni: M1n021

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,223
1,854
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
Che versione di GCC stai utilizzando?

Io ottengo tempi vicini a BrutPitt compilando con O3 (i suoi sono meglio ☹️):
Codice:
perm. succ. con classe   479001600 1410ms
perm. succ. senza classe 479001600 1566ms
std::next_permutation    479001600 2265ms

Altra esecuzione:

Codice:
perm. succ. con classe   479001600 1444ms
perm. succ. senza classe 479001600 1629ms
std::next_permutation    479001600 2359ms

Versione compilatore:
Codice:
gcc.exe (Rev1, Built by MSYS2 project) 12.2.0

Codice:
Target: x86_64-w64-mingw32

Se hai anche tu la nostra versione, almeno come release principale, penso proprio sia dovuto alla tua macchina. Che CPU hai, che non ricordo?
Stai compilando per x64?

Su due piedi, tirando un pò a "indovinare", mi viene da pensare che hai una CPU datata e che non supporti magari set di istruzioni che nel nostro caso invece il compilatore utilizza (in quanto supportati dalla CPU).
 
  • Mi piace
Reazioni: BrutPitt e M1n021

M1n021

Nuovo Utente
143
68
Non so se sia questo il modo corretto di verificare la versione del compilatore:

Codice:
C:\Program Files\CodeBlocks\MinGW\bin>g++ --version
g++ (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Stai compilando per x64?
Penso di sì.

Per quanto riguarda il PC ho un pentium dual core e5700! 😅


Ora non so a quale dei suddetti fattori siano imputabili i tempi che ottengo, ma in queste condizioni come posso fare ad ottimizzare il codice senza avere la possibilità di misurarne l'effettiva efficienza? 🤔
 

BrutPitt

Utente Attivo
1,166
1,262
Non so se sia questo il modo corretto di verificare la versione del compilatore:
g++ -v
ti da' un quadro completo anche delle varie opzioni attualmente in uso (e del "target" di default) ... pero' e' sinonimo/alias di --version

Bash:
g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 11.2.0-19ubuntu1'
--with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2
--prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=x86_64-linux-gnu-
--enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix
--libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes
--with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie
--with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch
--disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic
--enable-offload-targets=nvptx-none=/build/gcc-11-gBFGDP/gcc-11-11.2.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-gBFGDP/gcc-11-11.2.0/debian/tmp-gcn/usr
--without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
--with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.2.0 (Ubuntu 11.2.0-19ubuntu1)

Gia' che mi trovo... mi "scappa" di fare un'osservazione... 😇
Non che ci sia nulla di scorretto o sbagliato nel tuo codice, ma avendo il C++ la possibilita' di inizializzare le variabili membro direttamente da costruttore, sarebbe piu' elegante e leggibile scrivere:

C++:
A::A(const unsigned int n) : k(n)
{
    u = new unsigned int[n];
    ...
}

Io ottengo tempi vicini a BrutPitt compilando con O3 (i suoi sono meglio ☹️):
Ho meno cores (mi pare di ricordare) disponibili ... ma un IPC migliore (in single core) 😁

Ho eseguito il codice su un Celeron N5095 (tutt'altro che datato: Intel NUC 11th gen) ed ottengo questo:
Codice:
perm. succ. con classe   479001600 2445ms
perm. succ. senza classe 479001600 1622ms
std::next_permutation    479001600 1988ms

gcc version 11.2.0 (Ubuntu 11.2.0-19ubuntu1)
Quindi immagino incida la mancanza del set di istruzioni estese (i.e. Intel SSE4.1, Intel SSE4.2, etc) dei Celeron e Pentium
 
Ultima modifica:

M1n021

Nuovo Utente
143
68
g++ -v
ti da' un quadro completo anche delle varie opzioni attualmente in uso (e del "target" di default)
Ecco, se può essere utile:
Codice:
C:\Program Files\CodeBlocks\MinGW\bin>g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=C:/Program\ Files/CodeBlocks/MinGW/bin/../libexec/gcc/x86_64-w64-mingw32/8.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../../../src/gcc-8.1.0/configure --host=x86_64-w64-mingw32 --build=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --prefix=/mingw64 -
-with-sysroot=/c/mingw810/x86_64-810-posix-seh-rt_v6-rev0/mingw64 --enable-shared --enable-static --disable-multilib --enable-languages=c,c++,fortran,
lto --enable-libstdcxx-time=yes --enable-threads=posix --enable-libgomp --enable-libatomic --enable-lto --enable-graphite --enable-checking=release --
enable-fully-dynamic-string --enable-version-specific-runtime-libs --disable-libstdcxx-pch --disable-libstdcxx-debug --enable-bootstrap --disable-rpat
h --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-gnu-as --with-gnu-ld --with-arch=nocona --with-tune=core2 --with-li
biconv --with-system-zlib --with-gmp=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-mpfr=/c/mingw810/prerequisites/x86_64-w64-mingw32-stat
ic --with-mpc=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-isl=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-pkgversion='x8
6_64-posix-seh-rev0, Built by MinGW-W64 project' --with-bugurl=https://sourceforge.net/projects/mingw-w64 CFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/x
86_64-810-posix-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw
32-static/include' CXXFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/x86_64-810-posix-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x86_64
-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/include' CPPFLAGS=' -I/c/mingw810/x86_64-810-posix-seh-rt_v6-rev0/mingw64/o
pt/include -I/c/mingw810/prerequisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/include' LDFLAGS='-pipe -fno-i
dent -L/c/mingw810/x86_64-810-posix-seh-rt_v6-rev0/mingw64/opt/lib -L/c/mingw810/prerequisites/x86_64-zlib-static/lib -L/c/mingw810/prerequisites/x86_
64-w64-mingw32-static/lib '
Thread model: posix
gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)

Gia' che mi trovo... mi "scappa" di fare un'osservazione... 😇
Non che ci sia nulla di scorretto o sbagliato nel tuo codice, ma avendo il C++ la possibilita' di inizializzare le variabili membro direttamente da costruttore...
Hai ragione, se ben ricordo questa funzionalità si chiama "initializer list" o qualcosa del genere.
 

BAT

Moderatore
Staff Forum
Utente Èlite
22,944
11,580
CPU
1-Neurone
Dissipatore
Ventaglio
RAM
Scarsa
Net
Segnali di fumo
OS
Windows 10000 BUG
Per quanto riguarda il PC ho un pentium dual core e5700!
Ora non so a quale dei suddetti fattori siano imputabili i tempi che ottengo
entrambe le cose: compilatore vecchio e CPU ancora più vecchia, tuttavia la CPU vecchia in questi casi non è una cosa così deleteria: l'HW vecchio non può mascherare l'inefficienza di un algoritmo come invece può fare la potenza di una CPU da 50000 GHz 😆
ti è più facile valutare la bontà di quello che hai codificato già "ad occhio"!
Insomma chi ha un hw vecchio è maggiormente incentivato a scrivere algoritmi efficienti.

Però prima o poi cambialo quel dinosaurodi PC eh! 😅
 

M1n021

Nuovo Utente
143
68
l'HW vecchio non può mascherare l'inefficienza di un algoritmo come invece può fare la potenza di una CPU da 50000 GHz
Se parliamo in termini assoluti sono d'accordo, ma qui il problema nasce dal confronto di più codici sulla stessa macchina.

ti è più facile valutare la bontà di quello che hai codificato già "ad occhio"!
In linea di massima sì, e forse proprio per questo non riuscivo a darmi una spiegazione del perché la versione con la classe fosse così tanto più lenta di quella senza...

Però prima o poi cambialo quel dinosaurodi PC eh! 😅
Eh già! 😂


Ora non so a quale dei suddetti fattori siano imputabili i tempi che ottengo, ma in queste condizioni come posso fare ad ottimizzare il codice senza avere la possibilità di misurarne l'effettiva efficienza? 🤔
Magari dico una sciocchezza, visto che non so come funzionano, ma se usassi un compilatore online? E nel caso quale mi consigliate?
 

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,223
1,854
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
Quindi immagino incida la mancanza del set di istruzioni estese (i.e. Intel SSE4.1, Intel SSE4.2, etc) dei Celeron e Pentium

Ci ho preso quindi, anche secondo te è così. 🤪

Ora non so a quale dei suddetti fattori siano imputabili i tempi che ottengo, ma in queste condizioni come posso fare ad ottimizzare il codice senza avere la possibilità di misurarne l'effettiva efficienza? 🤔

Comunque GCC 8 si, è davvero vecchio, e la CPU... non è giovane.

Quello che utilizzo spesse volte io te l'ha linkato Brutpitt, Godbolt.
L'alternativa è utilizzare un disassembler o un debugger (più comodo probabilmente) e guardare direttamente dal tuo PC. Puoi scaricare x64dbg, che ti offre sia la versione per codice 32bit sia quella a 64bit.

Il vantaggio del guardare sul tuo PC è che vedi il codice prodotto dal tuo compilatore e per la tua CPU; se guardi su Godbolt è più facile cambiare versione, ma al 90% non riuscirai a produrre il codice che produce a te in locale (in quanto la CPU è datata e non supporta set che utilizza invece il compilatore online).

Lo svantaggio è che se debug con questi strumenti devi saper leggere un pò di assembly.

Volendo però, puoi anche produrre direttamente il codice assembly usando il compilatore facendo questo:
Codice:
gcc -S es.c -masm=intel
Ti verrà generato un file ".s". Il flag che vedi è per evitare AT&T e usare la sintassi di Intel.

I set a cui ho fatto riferimento in cima e che ha poi esplicitato Brutpitt sono quelle istruzioni un pò strane che avevo riportato nell'altro post, quelle che fanno uso dei registri XMM.
Fanno parte del set SIMD (Single Instruction Multiple Data), puoi leggere di più qui: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
Riprendo uno dei codici:

Codice:
        cmp     ebp, r12d
        jne     .L13
        movdqu  xmm0, XMMWORD PTR [rsp+40]
        movdqu  xmm1, XMMWORD PTR [rsp+40]
        lea     r12d, [rbp+0+rbp*4]
        pmuludq xmm1, XMMWORD PTR .LC4[rip]
        movdqa  xmm2, XMMWORD PTR .LC4[rip]
        pshufd  xmm1, xmm1, 8
        add     r12d, r12d
        psrlq   xmm0, 32
        mov     DWORD PTR [rsp+56], r12d
        psrlq   xmm2, 32
        pmuludq xmm0, xmm2
        movdqu  xmm2, XMMWORD PTR [rsp+24]
        pmuludq xmm2, XMMWORD PTR .LC5[rip]
        pshufd  xmm2, xmm2, 8
        pshufd  xmm0, xmm0, 8
        punpckldq       xmm1, xmm0
        movdqu  xmm0, XMMWORD PTR [rsp+24]
        movups  XMMWORD PTR [rsp+40], xmm1
        psrlq   xmm0, 32
        pmuludq xmm0, XMMWORD PTR [rsp]
        pshufd  xmm0, xmm0, 8
        punpckldq       xmm2, xmm0
        movups  XMMWORD PTR [rsp+24], xmm2
        jmp     .L13
 
  • Mi piace
Reazioni: BrutPitt

bigendian

Utente Attivo
749
431
OS
Linux
nel mio Ryzen 9 3900xt
Codice:
 gcc version 12.2.0 (GCC)
    ~/tests  g++ main.cc -o test
    ~/tests  ./test
perm. succ. con classe   479001600 6159ms
perm. succ. senza classe 479001600 6019ms
std::next_permutation    479001600 11430ms
    ~/tests  g++ main.cc -O3 -o test
    ~/tests  ./test
perm. succ. con classe   479001600 1459ms
perm. succ. senza classe 479001600 1577ms
std::next_permutation    479001600 3180ms

clang 14.0.6
    ~/tests  clang++ main.cc -o test
    ~/tests  ./test
perm. succ. con classe   479001600 7256ms
perm. succ. senza classe 479001600 5841ms
std::next_permutation    479001600 12727ms
    ~/tests  clang++ -O3 main.cc -o test
    ~/tests  ./test
perm. succ. con classe   479001600 899ms
perm. succ. senza classe 479001600 751ms
std::next_permutation
 

M1n021

Nuovo Utente
143
68
Per quanto riguarda i compilatori online pare che le risorse che mettono a disposizione siano troppo limitate per effettuare calcoli come quelli presenti nel codice qui postato.

A questo punto mi sembra di capire che l'unica alternativa sia quella di imparare un po' di assembly e provare a trarre qualche conclusione direttamente da quello... oppure me ne frego di rincorrere l'efficienza fattuale (rifacendomi ossia ai tempi di esecuzione riscontrati) e, in attesa di un PC migliore, mi limito a programmare andando "ad occhio"! 😅
 

BrutPitt

Utente Attivo
1,166
1,262
Ci ho preso quindi, anche secondo te è così. 🤪
Mi comincia a venire qualche dubbio... inizio a pensare che possa anche essere merito/colpa della versione dello g++ che con la v.12 si sia dato una "mossa".

Anche perche' sulla stessa macchina, stesso S.O., ma con clang, ottengo questo:
Codice:
perm. succ. con classe 479001600 666ms
perm. succ. senza classe 479001600 667ms
std::next_permutation    479001600 650ms

clang version 14.0.5 (Fedora 14.0.5-1.fc36)


Come controprova ho provato a farlo per n=13, con questi risultati:
Codice:
perm. succ. con classe   6227020800 8119ms
perm. succ. senza classe 6227020800 9127ms
std::next_permutation    6227020800 10155ms
clang version 14.0.5 (Fedora 14.0.5-1.fc36)

perm. succ. con classe   6227020800 13806ms
perm. succ. senza classe 6227020800 18597ms
std::next_permutation    6227020800 15421ms
gcc version 12.2.1 20220819 (Red Hat 12.2.1-2) (GCC)

Stasera (con un po' di tempo) voglio provare una versione precedente di g++, sempre sulla stessa macchina... perche' online, con g++ 11.2, ottengo questo (e non penso abbiano un Celeron o un Pentium, come server 😁 ):
Codice:
perm. succ. con classe   479001600 4203ms
perm. succ. senza classe 479001600 2204ms
std::next_permutation    479001600 2958ms
gcc version 11.2.0 (Compiler-Explorer-Build-gcc--binutils-2.36.1)



Per quanto riguarda i compilatori online pare che le risorse che mettono a disposizione siano troppo limitate per effettuare calcoli come quelli presenti nel codice qui postato.
A questo punto mi sembra di capire che l'unica alternativa sia quella di imparare un po' di assembly e provare a trarre qualche conclusione direttamente da quello... oppure me ne frego di rincorrere l'efficienza fattuale (rifacendomi ossia ai tempi di esecuzione riscontrati) e, in attesa di un PC migliore, mi limito a programmare andando "ad occhio"! 😅
E provare ad installare una distribizione Linux, cosi' che tu abbia un compilatore un po' piu' recente?
Sebbene se ci sia modo di farlo anche in Windows (clang):
 
Ultima modifica:
  • Mi piace
Reazioni: DispatchCode

bigendian

Utente Attivo
749
431
OS
Linux
Effficienza si misura se serve, o anche per imparare ... tuttavia c'e' poco da imparare disassemblando codice su un moderno pc, se non con il fine di imparare un po di assembly x86.

Il tempo dell'assembly eseguito cosi come e' scritto e' finito da tempo (bei tempi d'oro), a parte nei microcontrollori.
Moderne cpu possono eseguire codice fuori dall'ordine da te scritto, se per loro convenienza. Per questo si usano spesso le "memory barrier".
 

DispatchCode

Moderatore
Staff Forum
Utente Èlite
2,223
1,854
CPU
Intel I9-10900KF 3.75GHz 10x 125W
Dissipatore
Gigabyte Aorus Waterforce X360 ARGB
Scheda Madre
Asus 1200 TUF Z590-Plus Gaming ATX DDR4
HDD
1TB NVMe PCI 3.0 x4, 1TB 7200rpm 64MB SATA3
RAM
DDR4 32GB 3600MHz CL18 ARGB
GPU
Nvidia RTX 3080 10GB DDR6
Audio
Integrata 7.1 HD audio
Monitor
LG 34GN850
PSU
Gigabyte P850PM
Case
Phanteks Enthoo Evolv X ARGB
Periferiche
MSI Vigor GK30, mouse Logitech
Net
FTTH Aruba, 1Gb (effettivi: ~950Mb / ~480Mb)
OS
Windows 10 64bit / OpenSUSE Tumbleweed
A questo punto mi sembra di capire che l'unica alternativa sia quella di imparare un po' di assembly e provare a trarre qualche conclusione direttamente da quello... oppure me ne frego di rincorrere l'efficienza fattuale (rifacendomi ossia ai tempi di esecuzione riscontrati) e, in attesa di un PC migliore, mi limito a programmare andando "ad occhio"! 😅

Considera che le applicazioni che richiedono un'ottimizzazione tale da "preoccuparsi" di produrre codice che fa uso di SSE/AVX è una parte ridotta, non di certo tutte. Questo per dirti che non credo al giorno d'oggi che qualcuno si preoccupi se il proprio software desktop faccia uso di certe istruzioni o di altre; questo ovviamente per software che non deve eseguire importanti calcoli e/o utilizzare meno risorse possibili.

Ma detto ciò appoggio in toto sull'assembly. 😁 L'aspetto utile ad impararlo è che ti porta a conoscere altre cose che hanno a che fare con la CPU e in generale con l'architettura del pc (e anche con l'OS, in un certo senso... da un argomento iniziale vai poi ad esplorare).

Detto ciò, aggiungo altro: se vuoi essere SICURO che venga generato il codice in un certo modo (usando SSE ad esempio) puoi usare le intrinsics di Intel, le trovi qui: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html
Non ti serve scrivere in assembly (o assembly inline), ma ti appoggi a delle funzioni:
Codice:
__m128 _mm_rsqrt_ps (__m128 a);

Description
Compute the approximate reciprocal square root of packed single-precision (32-bit) floating-point elements in a, and store the results in dst. The maximum relative error for this approximation is less than 1.5*2^-12.

Operation

FOR j := 0 to 3
    i := j*32
    dst[i+31:i] := (1.0 / SQRT(a[i+31:i])) 
ENDFOR

Mi comincia a venire qualche dubbio... inizio a pensare che possa anche essere merito/colpa della versione dello g++ che con la v.12 si sia dato una "mossa".

Stasera (con un po' di tempo) voglio provare una versione precedente di g++, sempre sulla stessa macchina... perche' online, con g++ 11.2, ottengo questo (e non penso abbiano un Celeron o un Pentium, come server 😁 ):

Stavo per dirti la stessa cosa: se puoi, prova ad utilizzare la sua versione di g++, la 8 e qualcosa, così vediamo.
Non ho controllato che set supporti la sua CPU, ma è anche probabile che non supportasse qualche estensione; sicuramente i compilatori un pò più vecchi vanno a produrre codice meno efficiente però.

A quanto ricordo Clang produce anche codice migliore però.

E provare ad installare una distribizione Linux, cosi' che tu abbia un compilatore un po' piu' recente?

In realtà non è un problema quello: la versione del mio è presa da MSYS, ti porta già la versione più recente (non aggiorno da una decina di giorni o più).
Inoltre c'è anche il compilatore di Microsoft da provare, volendo.

Effficienza si misura se serve, o anche per imparare ... tuttavia c'e' poco da imparare disassemblando codice su un moderno pc, se non con il fine di imparare un po di assembly x86.
Il tempo dell'assembly eseguito cosi come e' scritto e' finito da tempo (bei tempi d'oro), a parte nei microcontrollori.
Moderne cpu possono eseguire codice fuori dall'ordine da te scritto, se per loro convenienza. Per questo si usano spesso le "memory barrier".

Si concordo.
Per altro vi è un duplice "problema": il codice che vai a scrivere in C/C++ o quello che è, non viene tradotto in assembly così come ti aspetteresti, ma subisce svariate ottimizzazioni che possono dipendere dalle versioni dei compilatori, e dai compilatori stessi. Quindi lasciar fare a loro è la cosa migliore per avere un codice performante (scrivere dell'assembly inline, salvo casi particolarissimi, non produrrà con buone probabilità codice migliore di quello che produrrebbe già il compilatore).

Proprio nell'altro topic avevo riportato una parte di codice che era stata prodotta diversamente dal compilatore, per motivi di efficienza (2 istruzioni che nel codice in C erano invertite); e avevo anche riportato una divisione, presente nel mio codice, che se spostata in un'altra parte del ciclo risultava più efficnete.

Il secondo "problema" è quello che dici tu bigendian: in x86/x86-64 il codice viene eseguito out-of-order e poi il risultato viene "rilasciato" quando serve, per non alterare quello che è il flusso logico del codice. Per assurdo scrivere del codice a mano, anche in questo caso, potrebbe portare a problemi di dipendenza che vanno a impattare sull'efficienza e quindi sul tempo di esecuzione.

MA, trovo sia cosa buona e giusta conoscere un pò di assembly, specie per quanto dicevo sopra. E poi... può anche portare ad "esplorare" un campo che ho sempre trovato affascinante, ovvero fare del "reverse engineering". 😁
 
Stato
Discussione chiusa ad ulteriori risposte.

Ci sono discussioni simili a riguardo, dai un'occhiata!

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!

Discussioni Simili