Test velocità algoritmi sui numeri primi

di il
15 risposte

Test velocità algoritmi sui numeri primi

Salve, stavo leggendo su un sito l'illustrazione di due tipi di algoritmi per cercare numeri primi;
il primo è molto semplice ed intuitivo, segue la definizione di numero primo (cioè un numero divisibile solo per se stesso) ed aveva questa impostazione (questo è una mia versione del codice perchè l'originale non funzionava per un errore, ma è lo stesso algoritmo a meno di nomenclature e ordine):

#include<stdio.h>

//Funzione di controllo
long long int controllo(long long int lim){
int i;
for(i=2;i<lim;i++)
    if(lim%i == 0)
    return 0;
    return 1;
}


int main(){

long long int i, lim, primo = 0;

printf("\tCalcolo numeri primi\n");
printf("\nInserire il limite superiore per la ricerca: ");
scanf("%lld",&lim);

//Calcoli
for(i=0;i<=lim;i++){
    if(controllo(i))
    printf("%lld\n\n",i);
}
    system("PAUSE");
    return 0;
}
Quindi opera un confronto con un altro algoritmo, basato sul primo, ma che usa un'altra sfaccettatura della definizione dei numeri primi, e cioè che per controllare se un numero è primo o no basta verificare che non sia divisibile solo per i primi più piccoli del numero da controllare. Cosa che facilita di molto il calcolo, ma a quanto pare non lo velocizza
Il codice è questo:

#include<stdio.h>
#include<stdlib.h>

long long int *primi,ii=2;
long long int primo(long long int num);

int main(){
    long long int i, numeri;
    primi = (long long int*)malloc(sizeof(long long int));
    primi[0] = 2;
    printf("\tCalcolo numeri primi\n");
    printf("\nInserisci il numero fino al quale vuoi arrivare: ");
    scanf("%lld",&numeri);

    for(i = 2; i<=numeri; i++){
        if(primo(i)){
			printf("\n%lld\n\n",i);
        }
	}

    //printf("\nDati salvati sul file di log.\n\n");

    system("PAUSE");
    return 0;
}

//Funzione di controllo
long long int primo(long long int num){
    primi = (long long int*)realloc(primi,sizeof(long long int)*ii);
    long long int i;
    long long int prim = 1;
    if(num == 2)
           return 1;
    for(i=0;i<ii-1;i++)
        if(num%primi[i] == 0)
            prim = 0;

    if (prim)
        primi[ii++-1] = num;
    return prim;
}
L'autore del post dice che il secondo codice, rispetto al primo, impiega molto meno tempo a trovare i numeri (e lui cita un range fino a 10000 numeri, che non sono neanche tanti per testarne le prestazioni).

Facendo la prova, se si eseguono i codici cosi come sono appare evidente dall'output che il secondo è molto più lento in realtà, addirittura aprendo due finestre del terminale e facendo partire prima quello con "l'ottimizzazione" e poi quello di base, quello di base lo supera subito e l'altro rimane indietro tantissimo, addirittura nell'oridine di 10^5 dopo qualche ora sui numeri trovati.

Ora io, a parte dei dubbi su come è scritto il secondo codice, che non capisco quando avviene il realloc del vettore e l'uso della variabile "ii", mi sono dato delle possibili spiegazioni (se funziona come si afferma e che non capisco):

1)la mia versione rispetto a quello ottimizzato ha solo bisogno di tempo per bloccarsi; cioè che da un certo valore in poi il ciclo for del primo codice sarà cosi sovraccarico che il secondo codice risulterà più veloce, ma verificando la cosa risulta poco attendibile perchè lasciandoli andare tutta la notte alla fine la versione di base è sempre la più veloce e soprattutto ha trovato un sacco di numeri in più rispetto all'altro, che invece rallenta molto più velocemente rispetto alla mia versione, quindi oltre ad essere in svantaggio sui primi trovati si trova anche in una condizione di divergenza sui tempi rispetto alla versione di base.

2) il realloc uccide la memoria (e fin qua sarebbe poco male con le ram odierne), impiegando anche un sacco di cicli ad eseguire il realloco della memoria, al punto che la quantità di cicli del clock per eseguire il for su un numero grande è una frazione rispetto a quelli necessari per eseguire il realloc (frazione molto piccola osservando gli output), cosa che non ho verificato perchè dovrei disassemblare il codice con la libreria stdlib implementata e vedere il codice in asm com'è, anche se sò per sentito dice che dal punto di vista dell'assembly le librerie del C sono poco ottimizzate con istruzioni della serie MOV EDI, EDI ecc.. che fanno perdere un sacco di tempo e potrebbero essere rimpiazzate da dei NOP (ma il punto non è questo).

Il punto è spiegarsi questa differenza, ma soprattutto perchè l'autore del post dove ho trovato quell'algoritmo dice che il secondo codice è più veloce quando, facendo un test, si vede che non è cosi.

Voi riscontrate problemi magari nel sorgente? E come potrebbero essere risolti?

P.S. i codici sono stati eseguiti su macchine diverse con linux e windows, compilati sempre con GCC e producono lo stesso output.

15 Risposte

  • Re: Test velocità algoritmi sui numeri primi

    Il problema e' l'allocazione di memoria, che viene fatto per OGNI intero che devi testare.

    Operazione assolutamente inutile e STRAPESANTE, ABBONDANTEMENTE piu' pesante di qualunque altra operazione necessaria per testare la primalita' di un numero.

    Se ti serve un blocco di memoria, allocane abbastanza in modo che sia necessario riallocarla SOLO dopo diversi minuti (o ore)!!! NON ogni microsecondo!!!!!!

    Lascia perdere i sentiti dire perche' quello che senti sono al 100% delle sciocchezze (a meno che a dirlo non sia uno che ci capisce veramente ).

    Tanto per andare nel dettaglio:

    allocare memoria non e' una cosa banale:

    la memoria va richiesta al sistema operativo, il quale deve gestire la memoria di TUTTI i processi in esecuzione sulla macchina.

    Ma non solo, anche se la memoria che richiedi la prendi da quella gia' assegnata al processo, l'implementazione DEVE tenere conto che la tua applicazione potrebbe essere multithreading, quindi deve assicurarsi che ci sia SEMPRE E SOLO un thread alla volta che la richiede.

    La sincronizzazione e' un'operazione pesante! Anche se c'e' un un'unico thread che la richiede.

    Ma non basta: la memoria allocata deve poter essere deallocata, quindi devono essere gestite delle strutture dati di servizio (visibili solo dalla libreria di allocazione) che servono per sapere QUALE blocco di memoria e' allocato, QUALE e' libero, di che dimensione, ecc.

    Quindi, come vedi, non e' minimamente un problema di efficienza.

    Nel 99% dei casi, i problemi di performance sono molto puntuali e raramente richiedono interventi banali per essere rimossi.

    Ad esempio, i tuoi algoritmi usati per testare la primalita' possono essere resi DECISAMNETE piu' efficienti!!!

    1) non serve confrontare il tuo numero (N) con tutti i numeri >= 2, ma SOLO con i numeri DISPARI >=3 (questo riduce della META' il numero di test, e quindi radoppio della velocita'!)
    2) non serve confrontare il tuo numero (N) con tutti i numeri DISPARI >=3 e < N, ma basta arrivare a K tale che K^2 <= N e (K+1)^2 > N.
    Si, e' la radice quadrata, ma visto che si usano SOLO gli interi, e la sqrt e' una funzione nel dominio dei reali o dei complessi, non l'ho usata
    Il motivo per arrivare SOLO a K e non a N e' OVVIO, basta pensare a che cosa e' un numero primo ed un numero composto!

    Questo fa si che per testare numeri che arrivano a 4000.000.000, ti basta testare fino a 65535!
    E contare fino a 65535 stai nanosecondi!

    3) nel secondo algoritmo, fai la realloc per OGNI intero che testi. E reallochi ESATTAMENTE la stessa quantita' di memoria!

    NON SERVE, la realloc la farai SOLO se trovi un nuovo primo!
    E per evitarla di farla troppo spesso, ricordati quanto scritto all'inizio del post.

    Dirai: ma la realloc e' stupida se non si accorge che voglio reallocare ESATTAMENTE la stessa quantita' di memoria, e quindi non devo reallocare niente!

    Vero! Probabilmente qui' ci potrebbe essere spazio per un infinitesimo miglioramento di performance.

    La questione banale e': ma quale e' la percentuale dei casi in cui uno realloca ESATTAMENTE la stessa quantita' di memoria? Lo 0.000...0001% dei casi?

    Qui' l'ottimizzazione non la deve fare la realloc, ma la testa del programmatore!
  • Re: Test velocità algoritmi sui numeri primi

    Concordo con tutto quello che hai detto, che è possibile ottimizzare ancora di più, ma quello che non capivo era come è stato scritto il realloc e se allocava ogni volta che trovava un primo o ad ogni iterazione.

    L'ho trovato qua:
    http://www.thecsea.it/tutorial/2010/05/calcolo-dei-numeri-primi/

    E' evidente che scrivono boiate, o esempi molto all'acqua di rose.
    Per quanto riguarda il fatto delle librerie del C ti posso assicurare che quell'opinione è di un mio amico molto esperto nella programmazione a basso livello, scriveva driver e programmi interamente in FASM perchè analizzando i codici in C notò che il codice deassemblato non era ottimizzato per niente, almeno non secondo gli standard della Intel che ha rilasciato vari manuali sulle ottimizzazioni dei programmi.
    Purtroppo non ci sentiamo più, sennò avrei chiesto a lui anche per questo caso.

    Comunque ti ringrazio per la risposta esauriente e tempestiva, era quello che sospettavo.
  • Re: Test velocità algoritmi sui numeri primi

    Shebang ha scritto:


    ...
    Per quanto riguarda il fatto delle librerie del C ti posso assicurare che quell'opinione è di un mio amico molto esperto nella programmazione a basso livello, scriveva driver e programmi interamente in FASM perchè analizzando i codici in C notò che il codice deassemblato non era ottimizzato per niente, almeno non secondo gli standard della Intel che ha rilasciato vari manuali sulle ottimizzazioni dei programmi.....
    Non so quanti anni di sviluppo software possa avere il tuo amico, ma non ci vuole molto per capire che una libreria scritta in assembler puo' essere piu' efficiente di una scritta in C.

    Il problema e':

    1) quanto piu' complicato e' scrivere in assembler rispetto al C?
    2) serve?

    Ottimizzare una funzione che viene chiamata diciamo 1 volta al secondo per migliorarne i tempi di esecuzione di un millisecond, non serve a nulla: non c'e' nessun vantaggio.

    Le ottimizzazioni vanno fatte dove servono e se migliorano le performance almeno del, diciamo, 25% (ma anche qui' dipende).
    Miglioramenti del 10% a fronte di uno sforzo di sviluppo del 50/100/1000% non ha molto senso.

    Nella maggior parte dei casi, un codice scritto bene in C e' efficiente tanto quanto del codice scritto in assembler, sopprattutto con i compilatori di ultima generazione.
  • Re: Test velocità algoritmi sui numeri primi

    In questo caso che si sa il massimo numero raggiungibile non sarebbe nemmeno necessario l'uso dell'heap.
    con 5 minuti di ottimizzazione viene fuori questo, che potrebbe essere ottimizzato ancora.
    
    #include <stdio.h>
    #include <stdlib.h>
    
    long long int primo(register long long int num, register long long int* prim, long long int* nprim, long long int *mprim)
    {
        if( num == 2 ) return 1;
        
        while( *prim && (num % *prim) ) ++prim;
        
        if ( *prim ) return 0;
        
        if ( *mprim <= *nprim + 2 ) 
    	 {
    		*mprim += 65536;
    		prim = (long long int*)realloc(prim,sizeof(long long int) **mprim);
    	 }
    	*(prim + *nprim) = num;
    	++(*nprim);
    	*(prim + *nprim ) = 0;
    	return 1;
    }
    
    
    int main()
    {
        register long long int i;
        register long long int numeri;
        register long long int* prim;
        long long int np = 1;
        long long int mp = 65536;
       
        prim = (long long int*)malloc(sizeof(long long int) * mp);
        *prim = 2;
        *(prim + 1) = 0;
        
        printf("\tCalcolo numeri primi\n");
        printf("\nInserisci il numero fino al quale vuoi arrivare: ");
        char buf[80];
        fgets(buf,80,stdin);
        if ( (numeri = atoi(buf)) <= 2 ) return -1;
        
        for(i = 2; i<=numeri; ++i)
        {
            if(primo(i, prim, &np , &mp))
    			printf("%lld\n",i);
        }
    
        return 0;
    }
    
    
  • Re: Test velocità algoritmi sui numeri primi

    migliorabile ha scritto:


    Shebang ha scritto:


    ...
    Per quanto riguarda il fatto delle librerie del C ti posso assicurare che quell'opinione è di un mio amico molto esperto nella programmazione a basso livello, scriveva driver e programmi interamente in FASM perchè analizzando i codici in C notò che il codice deassemblato non era ottimizzato per niente, almeno non secondo gli standard della Intel che ha rilasciato vari manuali sulle ottimizzazioni dei programmi.....
    Non so quanti anni di sviluppo software possa avere il tuo amico, ma non ci vuole molto per capire che una libreria scritta in assembler puo' essere piu' efficiente di una scritta in C.

    Il problema e':

    1) quanto piu' complicato e' scrivere in assembler rispetto al C?
    2) serve?

    Ottimizzare una funzione che viene chiamata diciamo 1 volta al secondo per migliorarne i tempi di esecuzione di un millisecond, non serve a nulla: non c'e' nessun vantaggio.

    Le ottimizzazioni vanno fatte dove servono e se migliorano le performance almeno del, diciamo, 25% (ma anche qui' dipende).
    Miglioramenti del 10% a fronte di uno sforzo di sviluppo del 50/100/1000% non ha molto senso.

    Nella maggior parte dei casi, un codice scritto bene in C e' efficiente tanto quanto del codice scritto in assembler, sopprattutto con i compilatori di ultima generazione.
    Dipende da cosa devi fare ovviamente. Alcune cose però sono arrivabili solo in assembly (senza parlare delle ottimizzazioni che per esperienza dico che un programma ben fatto in assembly pesa infinitamente meno di uno ben fatto in qualsiasi altro linguaggio con lo stesso scopo); quando studiai il FASM non si trattava di quanto fosse complicato, ma di capire sempre di più, un pò per volta, come funziona veramente un computer.

    Secondo me studiarlo e cimentarsi con questo linguaggio è un passo in avanti, passo che non ho finito di compiere purtroppo per i motivi che dicevi tu (complessità, pochi riferimenti per lo studio oltre ai manuali intel, mole di informazioni, altri impegni).
    Comunque sia il C è un signore per semplicità e potenza, ma l'assembly è la radice di tutto e come tale ha i suoi pregi (senza contare le difficoltà che bisogna attraversare per saperlo utilizzare al meglio).

    vbextreme ha scritto:


    In questo caso che si sa il massimo numero raggiungibile non sarebbe nemmeno necessario l'uso dell'heap.
    con 5 minuti di ottimizzazione viene fuori questo, che potrebbe essere ottimizzato ancora.
    
    #include <stdio.h>
    #include <stdlib.h>
    
    long long int primo(register long long int num, register long long int* prim, long long int* nprim, long long int *mprim)
    {
        if( num == 2 ) return 1;
        
        while( *prim && (num % *prim) ) ++prim;
        
        if ( *prim ) return 0;
        
        if ( *mprim <= *nprim + 2 ) 
    	 {
    		*mprim += 65536;
    		prim = (long long int*)realloc(prim,sizeof(long long int) **mprim);
    	 }
    	*(prim + *nprim) = num;
    	++(*nprim);
    	*(prim + *nprim ) = 0;
    	return 1;
    }
    
    
    int main()
    {
        register long long int i;
        register long long int numeri;
        register long long int* prim;
        long long int np = 1;
        long long int mp = 65536;
       
        prim = (long long int*)malloc(sizeof(long long int) * mp);
        *prim = 2;
        *(prim + 1) = 0;
        
        printf("\tCalcolo numeri primi\n");
        printf("\nInserisci il numero fino al quale vuoi arrivare: ");
        char buf[80];
        fgets(buf,80,stdin);
        if ( (numeri = atoi(buf)) <= 2 ) return -1;
        
        for(i = 2; i<=numeri; ++i)
        {
            if(primo(i, prim, &np , &mp))
    			printf("%lld\n",i);
        }
    
        return 0;
    }
    
    
    Lo stavo testando e purtroppo è crashato.
    Lo screen ->
  • Re: Test velocità algoritmi sui numeri primi

    Cosi dovrebbe andare
    
    
    long long int primo(register long long int num, register long long int* prim, long long int* nprim, long long int *mprim)
    {
        if( num == 2 ) return 1;
        
        long long int* mmprim = prim;
        
        while( *prim && (num % *prim) ) ++prim;
        
        if ( *prim ) return 0;
        
        if ( *mprim <= *nprim + 2 ) 
    	{
    		*mprim += 65536;
    		mmprim = (long long int*)realloc(mmprim,sizeof(long long int) **mprim);
                    if ( !mmprim )
                    {
    			puts("EOM");
    			exit(0);
                    }
    	}
    	
    	*(mmprim + *nprim) = num;
    	++(*nprim);
    	*(mmprim + *nprim ) = 0;
    	return 1;
    }
    
  • Re: Test velocità algoritmi sui numeri primi

    @shebang, stai ragionando troppo a basso livello.
    Le tue osservazioni non sono sbagliate, ma hai una visione ancora troppo limitata.

    Ti faccio alcuni esempi:

    la Fast Fourier Transform non e' fast perche' e' scritta in assembler, ma perche' sono stati ideati degli algoritmi estremamente intelligenti per calcolarla.

    I software di rendering realtime di scene virtuali 3D non sono veloci perche' scritti in assembler, ma perche' sono stati trovati degli algoritmi e strutture dati intelligenti che permettono di controllare le collisioni tra due triangoli in modo estremamente efficiente anche se la scena ha 3.000.000.000 (esatto, 3 miliardi) di triangoli!

    Pensa alle Google car!

    Quando hai un'applicazione che ha problemi di performance, non e' passando dal C/C++/Fortran all'assembler che risolvi i problemi, ma trovando algoritmi piu' intelligenti per risolvere in meno tempo le operazioni piu' lente.

    Ed e' questa la parte (estremamente) difficile che differenzia un programmatore da un architetto software.

    Questo non vuol dire che 'assembler non serva, ma se sai cosa sono le intrinsic functions (se non lo sai, leggi qui: https://en.wikipedia.org/wiki/Intrinsic_functio), ti rendi conto che praticamente non serve.

    Certo, ci sono casi in cui e' utile, ma sono mooooooolto specifici.

    Ad esempio i Core i7 di ultima generazione hanno istruzioni assembler utili ad implementare l'algoritmo di crittografia AES: utile per implementare software di cifratura realtime del disco.

    Le librerie per il calcolo in precisione arbitraria (https://gmplib.org, http://www.mpfr.org) sono implementate per la maggior parte in assembler.

    Ma i compilatori C/C++ hanno talmente tanti parametri di configurazioni/direttive/estensioni al linguaggio (tu conosci tutti i parametri supportati dalla __declspec del compilatore Microsoft? Dubito ), che, se utilizzate come si deve, permettono di generare codice buono tanto quanto quello scritto direttamente in assembler. Ed anche di piu'! Con un sacco di fatica in meno.
  • Re: Test velocità algoritmi sui numeri primi

    migliorabile ha scritto:


    @shebang, stai ragionando troppo a basso livello.
    Le tue osservazioni non sono sbagliate, ma hai una visione ancora troppo limitata.

    Ti faccio alcuni esempi:

    la Fast Fourier Transform non e' fast perche' e' scritta in assembler, ma perche' sono stati ideati degli algoritmi estremamente intelligenti per calcolarla.

    I software di rendering realtime di scene virtuali 3D non sono veloci perche' scritti in assembler, ma perche' sono stati trovati degli algoritmi e strutture dati intelligenti che permettono di controllare le collisioni tra due triangoli in modo estremamente efficiente anche se la scena ha 3.000.000.000 (esatto, 3 miliardi) di triangoli!

    Pensa alle Google car!

    Quando hai un'applicazione che ha problemi di performance, non e' passando dal C/C++/Fortran all'assembler che risolvi i problemi, ma trovando algoritmi piu' intelligenti per risolvere in meno tempo le operazioni piu' lente.

    Ed e' questa la parte (estremamente) difficile che differenzia un programmatore da un architetto software.

    Questo non vuol dire che 'assembler non serva, ma se sai cosa sono le intrinsic functions (se non lo sai, leggi qui: https://en.wikipedia.org/wiki/Intrinsic_functio), ti rendi conto che praticamente non serve.

    Certo, ci sono casi in cui e' utile, ma sono mooooooolto specifici.

    Ad esempio i Core i7 di ultima generazione hanno istruzioni assembler utili ad implementare l'algoritmo di crittografia AES: utile per implementare software di cifratura realtime del disco.

    Le librerie per il calcolo in precisione arbitraria (https://gmplib.org, http://www.mpfr.org) sono implementate per la maggior parte in assembler.

    Ma i compilatori C/C++ hanno talmente tanti parametri di configurazioni/direttive/estensioni al linguaggio (tu conosci tutti i parametri supportati dalla __declspec del compilatore Microsoft? Dubito ), che, se utilizzate come si deve, permettono di generare codice buono tanto quanto quello scritto direttamente in assembler. Ed anche di piu'! Con un sacco di fatica in meno.
    Guarda che mi trovi d'accordo con te quando dici che per migliorare le performance di un programma bisogna rendere più intelligente l'algoritmo. E l'intelligenza massima, nella mia modesta opinione, insieme all'ottimizzazione massima, ce l'hai rendendo intelligente un algoritmo scritto in assembly, perchè, come mi è capitato più di una volta, per soddisfare un capriccio o una curiosità, ti ritrovi davanti problemi strani da risolvere dove il linguaggio ad alto livello che usi non ti aiuta nella risoluzione, figuriamoci nell'ottimizzazione. Immagina poi un programma con gli accorgimenti, scorciatoie matematiche ecc che portavi come esempi ma usate con l'asm!

    Sarò limitato (forse il termine adatto è minimalista) ma ottimizzare un programma che è stato creato con un linguaggio ad alto livello ideato per risolvere problemi molto vari e generali ma che non fornisce strumenti chirurgici e ottimizzare un codice che di per se è ciò che fa realmente il processore (cioè l'asm) non è la stessa cosa. Tanto vale conoscere qualcosa dell'assembly che, oltre a illuminarti su certi aspetti dei processori, può essere utile anche nei reverse engineerig e nelle modifiche appunto chirurgiche.

    Poi ovviamente non puoi programmare i giochi o programmi simili interamente in assembly, sarebbe follia (anche se esistono alcuni arcade interamente in asm), soprattutto quelli moderni;
    dico solo che TEORICAMENTE la programmazione a basso livello, se fatta bene, produce il miglior eseguibile scrivibile.

    Mi è capitato di vedere programmi in fasm talmente ottimizzati che contenevano giusto le istruzioni necessarie per svolgere un compito senza le quali il processore diceva: "e adesso?" , perchè in asm si ha molta più libertà su come impostare un programma, puoi fare praticamente tutto in QUALSIASI modo. Fu proprio questo che mi spinse a vederne gli aspetti principali, la plasticità con cui una cosa può essere fatta seguendo sempre lo stesso algoritmo per la risoluzione del problema trattato.

    Poi per quanto riguarda i processori io ho una mia opinione molto personale; dici che il rendering realtime è ottimizzato dal punto di vista della logica degli algoritmi per poter funzionare e non con ottimizzazioni a basso livello e sono d'accordo con te (anche se sono presenti inline magari, non saprei sinceramente), ma molte cose sono possibili perchè sono stati sviluppati processori molto potenti negli ultimi anni; la mia opinione consiste nell'osservazione che IPOTETICAMENTE, se tutti i programmi fossero scritti in assembly ed ottimizzati, tutta questa potenza di calcolo sarebbe sprecata, o meglio, solo in beneficio.

    In poche parole processori sempre più potenti per linguaggi sempre più astratti che portano sempre meno ottimizzazione intrinsecamente perchè usano librerie provenienti da altri linguaggi ancora che potrebbero essere migliorate o che sono troppo generali per poter essere ottimizzate per un uso specifico.

    E grazie che nessuno guarda all'asm per velocizzare un programma, con velocità di elaborazione di svariati miliardi di operazioni al secondo. Cosa vuoi che sia un ciclo di clock in più a tali velocità, ma questo effetto, su lunghe elaborazioni o iterazioni, si propaga, tutto li.

    Poi ovviamente hai ragione tu quando mi dici che per progetti non specifici il C e le tecniche che hai citato sono soluzioni ottime e sanciscono l'abilità del programmatore nel risolvere problemi.

    Quello che dicevo era in linea teorica sulle utilità dell'asm, poi che si possano fare le stesse cose conoscendo l'asm oppure tutte le caratteristiche di un compilatore è un'altra cosa. Ovviamente sono d'accordo con tutto quello che dici, sennò andavo a scrivere i miei post in forum dedicati all'assembly xD
  • Re: Test velocità algoritmi sui numeri primi

    Ni: i tuoi esempi sono ancora legati al concetto di programmazione procedurale, ed alla presenza di un compilatore che converte in modo piu' o meno stupido un linguaggio ad alto livello (C, Forth, ho volutamente escluso C++ ) in linguaggio macchina.

    Ma il paradigma procedurale e' solo uno dei possibili modi di programmare.
    Paradigmi come la programmazione ad oggetti, funzionale, logica, a regole, ecc, sono estremamente piu' espressivi e ti permettono di programmare oggettti che fanno cose decisamente piu' intelligenti.

    Pensa all'intelligenza artificiale, ai computer algebra system, ai sistemi di visione, ...

    Certo che potrebbero essere scritti in assembler, ma lo sforzo per farlo sarebbe immane, umanamente impossibile!

    Ecco perche' sono nati i compilatori, i compilatori jit, i paradigmi di programmazione, ...:

    1) a scapito di una piccola perdita di performance, hai a disposizione un'enorme potenza espressiva che ti permette di concentrarti sull'algoritmo/problema e non sulla sua implementazione

    2) deleghi la traduzione ad un sistema automatico che non fa errori, ma, anzi, ti aiuta a trovare i tuoi errori

    3) deleghi la complessita' delle ottimizzazioni ad algoritmi che sono stati progettati da super esperti che hanno studiato esplicitamente questi argomenti

    4) non esiste assembler in grado di rendere piu' efficiente un algoritmo inerentemente inefficente

    Ad esempio, se implementi una funzione ricorsiva, il compilatore puo' convertire la chiamata ricorsiva in un jump (tail recursion) : tu ragioni e scrivi una funzione che e' umanamente comprensibile, il compilatore si arrangia a fare le ottimizzazioni del caso.

    Insomma, c'e' un concetto fondamentale che bisogna sempre tenere in considerazione:

    1) i programmi sono scritti dai programmatori
    2) i programmi evolvono
    3) i programmatori sbagliano
    4) i programmi sbagliano (vedere punto 3 )

    e' infinitamente piu' importante semplificare lo sviluppo software, la correzione degli errori, che massimizzare le performace.

    Che, comunque, sarebbero solo di qualche %.


    Nota: prova dare degli esempi di problemi in cui hai trovato delle difficolta' usando un linguaggio ad alto livello
  • Re: Test velocità algoritmi sui numeri primi

    migliorabile ha scritto:


    Ni: i tuoi esempi sono ancora legati al concetto di programmazione procedurale, ed alla presenza di un compilatore che converte in modo piu' o meno stupido un linguaggio ad alto livello (C, Forth, ho volutamente escluso C++ ) in linguaggio macchina.

    Ma il paradigma procedurale e' solo uno dei possibili modi di programmare.
    Paradigmi come la programmazione ad oggetti, funzionale, logica, a regole, ecc, sono estremamente piu' espressivi e ti permettono di programmare oggettti che fanno cose decisamente piu' intelligenti.

    Pensa all'intelligenza artificiale, ai computer algebra system, ai sistemi di visione, ...

    Certo che potrebbero essere scritti in assembler, ma lo sforzo per farlo sarebbe immane, umanamente impossibile!

    Ecco perche' sono nati i compilatori, i compilatori jit, i paradigmi di programmazione, ...:

    1) a scapito di una piccola perdita di performance, hai a disposizione un'enorme potenza espressiva che ti permette di concentrarti sull'algoritmo/problema e non sulla sua implementazione

    2) deleghi la traduzione ad un sistema automatico che non fa errori, ma, anzi, ti aiuta a trovare i tuoi errori

    3) deleghi la complessita' delle ottimizzazioni ad algoritmi che sono stati progettati da super esperti che hanno studiato esplicitamente questi argomenti

    4) non esiste assembler in grado di rendere piu' efficiente un algoritmo inerentemente inefficente

    Ad esempio, se implementi una funzione ricorsiva, il compilatore puo' convertire la chiamata ricorsiva in un jump (tail recursion) : tu ragioni e scrivi una funzione che e' umanamente comprensibile, il compilatore si arrangia a fare le ottimizzazioni del caso.

    Insomma, c'e' un concetto fondamentale che bisogna sempre tenere in considerazione:

    1) i programmi sono scritti dai programmatori
    2) i programmi evolvono
    3) i programmatori sbagliano
    4) i programmi sbagliano (vedere punto 3 )

    e' infinitamente piu' importante semplificare lo sviluppo software, la correzione degli errori, che massimizzare le performace.

    Che, comunque, sarebbero solo di qualche %.


    Nota: prova dare degli esempi di problemi in cui hai trovato delle difficolta' usando un linguaggio ad alto livello
    Ahahah sono d'accordissimo con te soprattutto per quanto riguarda l'aspetto logico di un algoritmo, che se non è ottimizzato quello puoi programmare anche in Gesù Cristo ma non migliori nulla.

    Per quanto riguarda quello che l'assembly mi ha risolto molto rapidamente erano vari comandi per la gestione delle stringhe quali ROR e ROL, l'uso dello XOR tra registri e altri aspetti come accessi alla memoria che avvengono più velocemente di altri programmi, il controllo totale del processore (la possibilità di "haltare" i core, per cosi dire, ma come si possono bloccare si possono anche gestire), iterazioni lunghe ottimizzate. In FASM il mio amico sviluppò un programma che trovava numeri primi solo con la somma e la differenza come operazioni aritmetiche (non mi chiedere come perchè ora come ora non me lo ricordo, era una cosa astuta, dovrei averlo in qualche archivio), era lento (meno di 2 minuti) ma la sua versione ottimizzata con gli aspetti matematici che dicevi e qualche altro accorgimento, riusciva a trovarne fino a 1 milione in meno di un minuto (l'ho ritrovato ora e testando la versione a 32 bit impiega 35 secondi, variabile da cpu a cpu ovviamente, io ho un dual core) e salvava tutto il risultato su un txt.
    Queste sono le cose che mi tornano in mente ora su tutto quello che sperimentammo io e conoscenti con l'assembly guidati da chi lo conosceva bene.
    P.S. Non considero neanche gli aspetti della programmazione di cui non è permesso parlare in questo forum caratteristici del reverse, come bypass di controlli, come evitarli, ecc.
  • Re: Test velocità algoritmi sui numeri primi

    Come vedi, lo hai dimostrato tu stesso: casi mooolto specifici .

    Ecco come avrebbe dovuto essere implementato il secondo algoritmo

    Questo macina 1.000.000 di primi in meno di 6 secondi (siamo piu' sui 4.5 )

    Mono-thread (quindi che sia un i3, un i5 o un i7 non cambia nulla) a 2.2GHz.

    Guarda caso, uso l'assembler
    Ma solo per evitare di sprecare tempo nella generazione dei log.

    Nota: e' scritto per GNU C/C++, non per Visual Studio.

    
    #include <iostream>
    #include <string.h>
    #include <stdio.h>
    
    typedef unsigned long long int_t;
    
    const size_t ALLOC_SIZE = 4*1048576;
    const int_t  TIME_DELTA = 2000000000ull;
    
    
    static __inline__ int_t rdtsc(void)
    {
        unsigned int hi, lo;
        __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
        return ( (int_t)lo)|( ((int_t)hi)<<32 );
    }
    
    
    struct prime_t
    {
        int_t prime;
        int_t square;
    };
    
    struct primes_t
    {
        prime_t *primes;
        size_t capacity;
        size_t count;
        size_t lastc;
    
        int_t timestamp;
    
        primes_t(){
            primes = new prime_t[ALLOC_SIZE];
            capacity = ALLOC_SIZE;
            count = 0;
    
            timestamp = rdtsc();
            lastc = 0;
    
            add_prime(2);
        }
    
        void add_prime(int_t p)
        {
            if (count == capacity)
            {
                prime_t *saved = primes;
    
                capacity += ALLOC_SIZE;
                primes = new prime_t[capacity];
                memcpy(primes, saved, count*sizeof(prime_t));
                delete[] saved;
    
                printf("           ... realloc %ul primes\n", capacity);
            }
    
            primes[count].prime = p;
            primes[count].square = p*p;
            ++count;
    
            int_t now = rdtsc();
            if (now - timestamp > TIME_DELTA) {
                time_t tt = time(0);
                tm *plt = localtime(&tt);
    
                printf("[%02d:%02d:%02d] Found %8lu primes (+%lu). Last prime: %lu\n",
                       plt->tm_hour, plt->tm_min, plt->tm_sec,
                       count, count-lastc,
                       primes[count-1].prime);
                timestamp = now;
                lastc = count;
            }
        }
    
        bool is_prime(int_t p)
        {
            for (size_t i=0; i<count; ++i)
            {
                if (primes[i].square > p)
                    return true;
                if (!(p % primes[i].prime))
                    return false;
            }
            return true;
        }
    };
    
    
    primes_t primes;
    
    const int_t MAXINT = ~int_t(0);
    
    int main() {
        {
            time_t tt = time(0);
            tm *plt = localtime(&tt);
            printf("[%02d:%02d:%02d] Find primes < 2^64-1!\n", plt->tm_hour, plt->tm_min, plt->tm_sec);
        }
    
        for (int_t p=3; p<MAXINT; p += 2)
        {
            if (primes.is_prime(p))
                primes.add_prime(p);
        }
    
        return 0;
    }
    
    
    [18:08:48] Find primes < 2^64-1!
    [18:08:49] Found   269338 primes (+269338). Last prime: 3790229
    [18:08:50] Found   440511 primes (+171173). Last prime: 6433129
    [18:08:51] Found   597557 primes (+157046). Last prime: 8921417
    [18:08:52] Found   736258 primes (+138701). Last prime: 11157889
    [18:08:53] Found   866275 primes (+130017). Last prime: 13280017
    [18:08:54] Found   987445 primes (+121170). Last prime: 15276841
    [18:08:54] Found  1103469 primes (+116024). Last prime: 17203393
    [18:08:55] Found  1214915 primes (+111446). Last prime: 19065259
    [18:08:56] Found  1321949 primes (+107034). Last prime: 20864783
    [18:08:57] Found  1425122 primes (+103173). Last prime: 22609007
    [18:08:58] Found  1523168 primes (+98046). Last prime: 24272203
    [18:08:59] Found  1619732 primes (+96564). Last prime: 25918433
    [18:09:00] Found  1714251 primes (+94519). Last prime: 27534691
    [18:09:01] Found  1807589 primes (+93338). Last prime: 29136409
    [18:09:02] Found  1897738 primes (+90149). Last prime: 30684653
    [18:09:03] Found  1986236 primes (+88498). Last prime: 32215199
    [18:09:04] Found  2072035 primes (+85799). Last prime: 33699313
    [18:09:04] Found  2157448 primes (+85413). Last prime: 35186153
    [18:09:05] Found  2241545 primes (+84097). Last prime: 36648737
    [18:09:06] Found  2324280 primes (+82735). Last prime: 38091971
    [18:09:07] Found  2406436 primes (+82156). Last prime: 39522437
    [18:09:08] Found  2484769 primes (+78333). Last prime: 40894397
    [18:09:09] Found  2563212 primes (+78443). Last prime: 42272719
    [18:09:10] Found  2641472 primes (+78260). Last prime: 43649449
    [18:09:11] Found  2719107 primes (+77635). Last prime: 45016337
    [18:09:12] Found  2795578 primes (+76471). Last prime: 46363459
    [18:09:13] Found  2871379 primes (+75801). Last prime: 47703121
    [18:09:14] Found  2946561 primes (+75182). Last prime: 49036159
    [18:09:14] Found  3018164 primes (+71603). Last prime: 50302607
    [18:09:15] Found  3087565 primes (+69401). Last prime: 51533803
    [18:09:16] Found  3159439 primes (+71874). Last prime: 52811207
    [18:09:17] Found  3227780 primes (+68341). Last prime: 54028411
    [18:09:18] Found  3296878 primes (+69098). Last prime: 55261093
    [18:09:19] Found  3366408 primes (+69530). Last prime: 56501219
    [18:09:20] Found  3433867 primes (+67459). Last prime: 57704249
    [18:09:21] Found  3502462 primes (+68595). Last prime: 58929883
    [18:09:22] Found  3569251 primes (+66789). Last prime: 60128683
    [18:09:23] Found  3635803 primes (+66552). Last prime: 61320859
    [18:09:24] Found  3702970 primes (+67167). Last prime: 62526251
    [18:09:24] Found  3769692 primes (+66722). Last prime: 63725447
    [18:09:25] Found  3836193 primes (+66501). Last prime: 64921699
    [18:09:26] Found  3901821 primes (+65628). Last prime: 66102053
    [18:09:27] Found  3967148 primes (+65327). Last prime: 67277443
    [18:09:28] Found  4030737 primes (+63589). Last prime: 68422843
    [18:09:29] Found  4094268 primes (+63531). Last prime: 69569233
    [18:09:30] Found  4157160 primes (+62892). Last prime: 70706369
               ... realloc 8388608l primes
    [18:09:31] Found  4217393 primes (+60233). Last prime: 71796437
    [18:09:32] Found  4278698 primes (+61305). Last prime: 72903011
    [18:09:33] Found  4340029 primes (+61331). Last prime: 74014217
    [18:09:34] Found  4402720 primes (+62691). Last prime: 75153017
    [18:09:35] Found  4463945 primes (+61225). Last prime: 76261019
    [18:09:35] Found  4524304 primes (+60359). Last prime: 77358583
    [18:09:36] Found  4584603 primes (+60299). Last prime: 78457123
    [18:09:37] Found  4645096 primes (+60493). Last prime: 79557347
    [18:09:38] Found  4704284 primes (+59188). Last prime: 80635333
    [18:09:39] Found  4764476 primes (+60192). Last prime: 81728147
    [18:09:40] Found  4824192 primes (+59716). Last prime: 82816147
    [18:09:41] Found  4883762 primes (+59570). Last prime: 83903759
    [18:09:42] Found  4942642 primes (+58880). Last prime: 84980257
    [18:09:43] Found  5001784 primes (+59142). Last prime: 86060323
    [18:09:44] Found  5060076 primes (+58292). Last prime: 87126211
    [18:09:45] Found  5118000 primes (+57924). Last prime: 88185431
    [18:09:45] Found  5176392 primes (+58392). Last prime: 89252357
    [18:09:46] Found  5233769 primes (+57377). Last prime: 90305923
    
    Bisognerebbe implementarlo in assembler e vedere quanto si migliora.
  • Re: Test velocità algoritmi sui numeri primi

    
    /*
    Ottimizzazioni:
    - controllo esclusivo dei numeri dispari;
    - elaborazione fino alla radice del numero da controllare (riduzione drastica del numero di iterazioni);
    */
    
    #include <stdio.h>
    #include <math.h>		/* Opzionale; rimpiazzabile con l'espansione di Taylor della radice */
    
    long long int controllo(register long long int num){
        register long long int i;
    	register long long int tmp;
    
    	tmp = sqrt(num);				/* Radice del numero da controllare */
        for(i=2; i<=tmp; i++)
            if(i%2 != 0 && num%i == 0)	/* Limite dei controlli da eseguire e test della primalità */
                return 0;
                return 1;
    }
    
    
    int main(){
    
         long long int i, lim;
    
        //Dati
        printf("\n\t***********************************\n");
        printf("\t*                                 *\n");
        printf("\t*   Calcolatore di numeri primi   *\n");
        printf("\t*                                 *\n");
        printf("\t***********************************\n");
        printf("\nInserire il numero al quale si vuole arrivare nella ricerca: ");
        scanf("%lld",&lim);
        //char log[] = "Primi trovati.txt";
    
        //Calcoli e scrittura su file
        printf("\n\aATTENZIONE: la durata del calcolo dipende dal limite impostato!\n\tCalcolo in corso, attendere prego...\n");
        //FILE *fp;				/* Scrittura su file invece che a schermo; ottimizzazione dei tempi */
        //fp = fopen(log,"w");
    
        for(i=1;i<=lim;i++){
    		if(i%2 != 0)				/* Esclusione dei numeri pari */
            if(controllo(i))
                //fprintf(fp,"%lld\n",i);
                printf("%lld\n", i);
        }
        //fclose(fp);
        printf("\nRicerca completata.\n\n\a\a\a");
    
            system("PAUSE");
            return 0;
    }	/* La stampa sul terminale rallenta l'esecuzione; togliere dai commenti la scrittura su file per ottimizzare */
    
    E' una bomba, fino ad un milione ci mette 0.8 secondi su un computer di medie prestazioni e fino ad un miliardo, cosa impensabile senza tutte le ottimizzazioni del caso, ci mette meno di 5 ore.
  • Re: Test velocità algoritmi sui numeri primi

    Lo hai confrontato con quello che ho postato io, giusto come confronto?
  • Re: Test velocità algoritmi sui numeri primi

    migliorabile ha scritto:


    Lo hai confrontato con quello che ho postato io, giusto come confronto?
    Ancora no
Devi accedere o registrarti per scrivere nel forum
15 risposte