Perché il tempo di esecuzione è diverso ogni volta in java?

1

Sto cercando di creare un programma in Java che misuri la complessità di un programma specifico rispetto al suo tempo di esecuzione. Prendiamo un problema e codifichiamo quel problema in tutti i modi possibili rispetto alla complessità del tempo, diciamo che possiamo scrivere codice per un problema specifico in O (n), O (log (n)), O (n log (n)) e O (n ^ 2).

Registrerò il tempo di esecuzione di ciascun caso e creerò una scala con questi valori per misurare la complessità temporale della soluzione di altri. Come

  1. O (n) - > 2-3 ms
  2. O (log (n)) - > 1-2 ms ecc.

Ora se altro codice ha un tempo di esecuzione di 2,3 ms, allora il mio programma Java dirà che la soluzione data ha una complessità temporale dell'ordine O (n).

Ora il problema è che, dato che il mio codice è in Java, ogni volta che eseguo qualsiasi programma usando i comandi di Ubuntu attraverso il codice Java, ogni volta ottengo il tempo di esecuzione diverso e il tempo di esecuzione è molto ampio da 0,364246 a 0,902362. / p>

Perché sta accadendo e cosa posso fare per fare una scala efficiente?

Ecco il mio codice:

import java.io.*;

class ps_ef
{
    public static void main(String args[])
{
    String s=null,file_name,extension;
    int pos = args[0].lastIndexOf(".");

    extension = args[0].substring(pos+1);
    file_name = args[0].substring(0,pos);

    int lang = 0; //  1 -> c,c++ , 2 -> java

    try
    {   

        Process compile = null;

        switch(extension)
        {
            case "c"    :    compile = Runtime.getRuntime().exec("gcc -g "+ args[0] + " -o "+file_name);
                        lang = 1;           
                        break;
            case "c++"  :    compile = Runtime.getRuntime().exec("g++ -g "+ args[0] + " -o "+file_name);
                        lang = 1;
                        break;
            case "java" :    compile = Runtime.getRuntime().exec("javac "+ args[0]);
                        lang = 2;
        }

        BufferedReader stdError = new BufferedReader(new InputStreamReader(compile.getErrorStream()));

        if((s = stdError.readLine()) != null)
        {
            System.out.println("Compile Time Error OR Warning : ");

            System.out.println(s);
            while((s = stdError.readLine()) != null)
            {
                System.out.println(s);
            }
        }

        double startTime, run_time;
        Process run;

        if(lang == 1)
        {
             startTime = System.nanoTime();

             run = Runtime.getRuntime().exec("./"+file_name);

             run_time = (System.nanoTime()-startTime)/(double)Math.pow(10,6);
        }
        else
        {

            startTime = System.nanoTime();

             run = Runtime.getRuntime().exec("java "+file_name);

             run_time = (System.nanoTime()-startTime)/(double)Math.pow(10,6);
        }

        System.out.println("RunTime : "+ run_time+" ms");

        BufferedReader run_stdInput = new BufferedReader(new InputStreamReader(run.getInputStream()));

        BufferedReader run_stdError = new BufferedReader(new InputStreamReader(run.getErrorStream()));

        if(( s = run_stdError.readLine()) != null)
        {
            System.out.println("Runtime Error : ");

            System.out.println(s);

            while((s = run_stdError.readLine()) != null )
            {
                System.out.println(s);
            }
        }
        else if((s = run_stdInput.readLine()) != null)
        {
            String s_string = null;
            int failed = 0;

            File fs = new File(file_name+".txt");

            BufferedReader br  = new BufferedReader(new FileReader(fs));

            if((!s.equals(s_string = br.readLine())))
            {
                failed = 1;
            }

            while(((s = run_stdInput.readLine()) != null) & ((s_string = br.readLine()) != null) & (failed == 0))
            {
                if(!s.equals(s_string) )
                {
                    failed = 1;
                    break;
                }
            }

            if((failed == 1) || s != null || s_string != null)
            {
                System.out.println("Submmision Failed : ");
                System.out.println("Either Output Is Wrong.\nOR\nYour Output Is Not According To The Given Format. ");
                System.exit(0);

            }
            else
            {
                System.out.println("Submission Successful.");
            }

        }
    }   
    catch(IOException e)
    {
        System.out.println("Some Error Has Occured : ");
        e.printStackTrace();
        System.exit(-1);
    }

}

}

Ecco il mio output di run time:

    
posta nitin 29.12.2013 - 12:15
fonte

1 risposta

8

Il benchmarking è una scienza difficile. Sembra che il tuo computer stia cospirando contro di te:

  • La tua CPU conserva dati spesso richiesti in cache. Poiché l'accesso alla memoria in una cache è più rapido rispetto all'utilizzo di altra memoria, questo può distorcere i benchmark.
  • Il sistema operativo può memorizzare nella cache i file in memoria anziché caricarli dal disco. Ovviamente questo non è successo la prima volta che esegui un programma. Le esecuzioni successive dovrebbero essere più veloci.
  • I kernel del sistema operativo con condivisione del tempo come Linux, Mach o Windows NT si destreggiano tra più processi contemporaneamente. Ciò significa che altri processi possono essere eseguiti tra le fasi di esecuzione del programma di riferimento. Pertanto misurare il tempo trascorso è impreciso (probabilmente include anche il tempo di esecuzione da altri processi). Dovresti invece misurare la CPU utilizzata. D'altra parte ciò diventa inaccurato se il benchmark non è associato alla CPU.

    La misurazione del tempo trascorso rispetto al tempo della CPU è importante anche quando si considerano programmi multi-thread eseguiti su più core. Un programma multi-thread può essere eseguito "più veloce", ma utilizza più tempo CPU rispetto a una soluzione a thread singolo.

    La pianificazione non influisce solo su come misurare i tempi, ma può anche invalidare le cache.

I programmi di benchmark scritti sulla JVM sono ancora più complicati perché

  1. Ha un tempo di avvio enorme che deve essere sottratto per ottenere il tempo di esecuzione effettivo
  2. Può interpretare il programma (lento) o compilarlo su un codice macchina altamente ottimizzato (richiede un attimo, quindi estremamente veloce), oppure può tornare all'interpretazione. Quindi, mentre per molti usi, Java è più lento di C, può potenzialmente essere più veloce.
  3. La raccolta dei dati inutili può interrompere l'esecuzione in qualsiasi momento per analizzare la memoria.

Ci sono molte impostazioni che possono influenzare le prestazioni della JVM; farlo correttamente è difficile.

Ora diciamo che hai capito come ottenere misure corrette . Possiamo quindi risolvere i problemi relativi al caching dei file semplicemente eseguendo il programma più volte e scartando la prima esecuzione. I problemi relativi alla pianificazione possono essere ridotti al minimo eseguendo i benchmark in un ambiente controllato con pochi processi (ad esempio, eseguendo in modalità utente singolo senza un ambiente grafico).

Ora possiamo raccogliere una serie di punti dati per ogni programma. Soprattutto sulla JVM è importante eseguire il codice ripetutamente fino a quando non si sono verificate tutte le ottimizzazioni in modo che le misurazioni siano approssimativamente convergenti su un livello. Uno strumento utile per questo è il valore medio delle ultime misurazioni n .

Nel riportare il tempo medio di esecuzione, non solo riportiamo il valore medio di tutte le esecuzioni, ma anche la deviazione standard (talvolta chiamata sigma ) che mostra quanto coerenti sono le misure. È preferibile un valore inferiore. La SD è normalizzata sul valore medio, quindi è paragonabile solo per lo stesso valore medio, non tra le corse con mezzi diversi.

Se vuoi mostrare che un programma ha una complessità temporale specifica, devi eseguirlo con input di dimensioni diverse (es. n = 0, ..., 1.000.000 elementi in passi di 100.000). Per ogni dimensione di input, registrerai una o più misurazioni e quindi inserirai diverse equazioni nel set di dati:

O(1):       t = a
O(log n):   t = a * log(n)/log(b) + c    likely special cases a=2 or a=e
O(n):       t = a * n   + b
O(n log n): t = a * n * log(n)/log(b) + c
O(n^2):     t = a * n^2 + b
O(n^3):     t = a * n^3 + b
O(a^n):     t = a^(b * n) * c + d    likely special case of a=2
...

Per ogni adattamento, è quindi possibile calcolare la distanza tra l'adattamento e il set di dati. La classe di complessità con la più piccola distanza quadrata dal set di dati è probabilmente la classe di complessità dell'algoritmo utilizzato.

L'implementazione della curva di adattamento è quasi non banale come il benchmark corretto, quindi dovresti probabilmente usare un'implementazione esistente (io uso Python con scipy per questo tipo di lavoro).

Si noti che nei casi complessi il tempo di esecuzione osservato può essere la somma dei tempi di esecuzione di algoritmi con complessità diverse, ma la più grande classe di complessità dovrebbe dominare su input sufficientemente grandi.

    
risposta data 29.12.2013 - 13:32
fonte

Leggi altre domande sui tag