Benchmarks

Aus Byte-Welt Wiki
Wechseln zu: Navigation, Suche


Ich poste mal ein absichtlich stark vereinfachtes und leicht zu bedienendes Benchmarkingsystem. Es basiert nur auf zwei Klassen, nämlich dem eigentlichen System namens QuickBench und einer Schnittstelle namens Bench, das den einzelnen Benchmark darstellt. Ich denke die Benutzung ist wirklich einfach. Benches, die auf einer Objektstruktur arbeiten und mit unterschiedlich vielen Objekten gebencht werden sollen sind ebenso möglich, wie Benches, die nur eine bestimmte Funktion messen sollen, z.B. fib oder fac() und Co.

Das System erhebt keinesfalls den Anspruch perfekt implementiert oder jetzt der neue Meilenstein in Sachen benchmarking zu sein. Falls jemandem Fehler auffallen oder konstruktive Kritik besteht, bitte einfach kommentieren.

Das Interface Bench:

/*
 * Bench.java
 *
 * 03.06.2010
 *
 * (c) fastjack
 */
 
package blog.benchmarking;
 
/**
 * Diese Schnittstelle beschreibt einen Benchmark.
 *
 * @author fastjack
 * @version 1.0
 */
public interface Bench {
 
    /**
     * @return der Name.
     */
    String getName();
 
    /**
     * setzt den Benchmark und alle Resourcen, die er benutzt in den Urzustand zurück.
     */
    void reset();
 
    /**
     * Bereitet den Benchmark zur Ausführung vor.
     *
     * @param lm Parameter anhand dessen der Benchmark vorbereitet wird.
     */
    void prepare(long lm);
 
    /**
     * Führt den Benchmark aus.
     *
     * @param lm Parameter anhand dessen der Benchmark ausgeführt wird.
     */
    void execute(long lm);
}

Die abstrakte Klasse Bench:

/*
 * AbstractBench.java
 *
 * 06.01.2011
 *
 * (c) fastjack
 */
 
package blog.benchmarking;
 
/**
 * Diese Klasse stellt einen abstrakten Benchmark dar.
 *
 * @author fastjack
 * @version 1.0
 */
public abstract class AbstractBench implements Bench {
 
    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }
 
    @Override
    public void prepare(long lm) {
    }
 
    @Override
    public void reset() {
    }
 
}

Das Benchmarksystem QuickBench:

/*
 * QuickBench.java
 *
 * 03.06.2010
 *
 * (c) fastjack
 */
 
package blog.benchmarking;
 
import java.util.*;
 
/**
 * Diese kleine Klasse soll ein einfaches Benchmarksystem darstellen.
 *
 * @author fastjack
 * @version 1.0
 */
public class QuickBench {
 
    private final static String headerNxMSep = "+---------------------------+----------+-------"
            + "-----+----------+----------+-----------------+-----------------+";
    private final static String headerNxM = "| %-25s | %8s | %10s | %8s | %8s | %15s | %15s "
            + "|\n";
    private final static String headerNxIntervallSep = "+---------------------------+----------"
            + "+------------+------------+----------+----------+-----------------+-------------"
            + "----+";
    private final static String headerNxIntervall = "| %-25s | %8s | %10s | %10s | %8s | %8s | "
            + "%15s | %15s |\n";
    private final static String patternNxM = "| %-25s | %,8d | %,10d | %,8d | %,8d | %,15d | "
            + "%,15d |\n";
    private final static String patternNxMWNT = "| %-25s | %,8d | %,10d | %,8d | %,8d | %15s | "
            + "%15s |\n";
    private final static String patternNxIntervall = "| %-25s | %,8d | %,10d | %,10d | %,8d | "
            + "%,8d | %,15d | %,15d |\n";
    private final static String patternNxIntervallWNT = "| %-25s | %,8d | %,10d | %,10d | %,8d "
            + "| %,8d | %15s | %15s |\n";
 
    private QuickBench() {
    }
 
    /**
     * Führt Benchmarks, die auf Funktionen etc. aufbauen und einen numerischen Parameter
     * als Eingabe benötigen, aus. Diese Parameter werden in einem Intervall angegeben. Der
     * Benchmark wird zurerst zurückgesetzt aber nicht vorbereitet. Bevor das Benchmarking
     * gestartet wird, wird ein warmup zu allen Benchmarks aufgerufen und wie durch den
     * Parameter wm angegeben, wiederholt. Die Anzahl an Durchläufen wird im Feld n
     * angegeben. Alle Benchmarks werden zu jedem Eintrag e in n i-mal (i <= e) wiederholt,
     * jeweils mit einem Wert aus dem dem angegebenen Intervall.
     *
     * @param minMs minimale Millesekunden, bei denen Nanosekunden angezeigt werden.
     * @param wm WarmUp-Iterationen.
     * @param n Anzahl an Iterationen.
     * @param mmin Minimum des Intervalls.
     * @param mmax Maximum des Intervalls.
     * @param ba Benchmarks.
     */
    public static void benchNxIntervall(long minMs, long wm, long[] n, long mmin, long mmax,
            Bench[] ba) {
        QuickBench qb = new QuickBench();
        qb.warmup(ba, wm);
        System.out.println(headerNxIntervallSep);
        System.out.printf(headerNxIntervall, "Bench", "Rounds", "min", "max", "ms",
                "ms (avg)", "nt", "nt (avg)");
        System.out.println(headerNxIntervallSep);
        for (int i = 0; i < ba.length; i++) {
            Bench b = ba[i];
            qb.executeIntervall(b, n, minMs, mmin, mmax);
        }
        System.out.println(headerNxIntervallSep);
        qb.showInfo();
    }
 
    /**
     * F&uuml;hrt Benchmarks, die z.B. einer Anzahl von Objekten basieren, aus. Der Benchmark
     * wird zurerst zur&uuml;ckgesetzt und jeweils zu einem Wert aus m vorbereitet. Bevor das
     * Benchmarking gestartet wird, wird ein warmup zu allen Benchmarks aufgerufen und wie durch
     * den Parameter wm angegeben, wiederholt. Die Anzahl an Durchl&auml;ufen wird im Feld n
     * angegeben. Die Anzahl der Objekte im Feld m. Alle Benchmarks werden zu jedem Eintrag m0
     * in m i-mal (i <= m0) durchlaufen und zu jedem Eintrag n0 in n j-mal (j <= n0)
     * zu dem Wert m0 ausgef&uuml;hrt.
     *
     * @param minMs minimale Millesekunden, bei denen Nanosekunden angezeigt werden.
     * @param wm WarmUp-Iterationen.
     * @param n Anzahl an Iterationen.
     * @param m Anzahl an z.B. Objekten.
     * @param ba Benchmarks.
     */
    public static void benchNxM(long minMs, long wm, long[] n, long[] m, Bench[] ba) {
        QuickBench qb = new QuickBench();
        qb.warmup(ba, wm);
        System.out.println(headerNxMSep);
        System.out.printf(headerNxM, "Bench", "Rounds", "Objects", "ms", "ms (avg)",
                "nt", "nt (avg)");
        System.out.println(headerNxMSep);
        for (int i = 0; i < ba.length; i++) {
            Bench b = ba[i];
            for (int ii = 0; ii < m.length; ii++) {
                qb.execute(b, n, minMs, m[ii]);
            }
        }
        System.out.println(headerNxMSep);
        qb.showInfo();
    }
 
    /*
     * Zeigt Informationen zum System an.
     */
    private void showInfo() {
        System.out.println("java version " + System.getProperty("java.version"));
        System.out.println(System.getProperty("java.runtime.name") + "(build " +
                System.getProperty("java.runtime.version") + ")");
        System.out.println(System.getProperty("java.vm.name") + "(build "
                + System.getProperty("java.vm.version") + ", "
                + System.getProperty("java.vm.info") + ")");
        System.out.println(System.getProperty("os.name") + "("
                + System.getProperty("os.version") + ") " + System.getProperty("os.arch"));
        System.out.println("Max memory: " + Runtime.getRuntime().maxMemory() / (1024 * 1024)
                + "MB");
    }
 
    /*
     * F&uuml;hrt ein warmup zu allen Benchmarks durch. Dabei werden die Benches jeweils mit den
     * Werten 1 vorbereitet und ausgef&uuml;hrt.
     *
     * @param ba Benchmarks.
     * @param n Anzahl an Durchl&auml;ufen zum WarmUp.
     */
    private void warmup(Bench[] ba, long n) {
        for (int i = 0; i < n; i++) {
            for (int ii = 0; ii < ba.length; ii++) {
                Bench b = ba[ii];
                b.reset();
                b.prepare(1);
                b.execute(1);
            }
        }
    }
 
    /*
     * F&uuml;hrt Benchmarks, die auf Funktionen etc. aufbauen und einen numerischen Parameter
     * als Eingabe ben&ouml;tigen, aus. Diese Parameter werden in einem Intervall angegeben. Der
     * Benchmark wird zurerst zur&uuml;ckgesetzt aber nicht vorbereitet.
     *
     * @param b Benchmark.
     * @param n Iterationen.
     * @param minMs minimale Millesekunden, bei denen Nanosekunden angezeigt werden.
     * @param mmin Minimum des Intervalls.
     * @param mmax Maximum des Intervalls.
     */
    private void executeIntervall(Bench b, long[] n, long minMs, long mmin, long mmax) {
        for (int i = 0; i < n.length; i++) {
            long ln = n[i];
            long ms = 0;
            long nt = 0;
            long ms0 = 0;
            long nt0 = 0;
            for (int ii = 0; ii < ln; ii++) {
                for (long l = mmin; l < mmax; l++) {
                    b.reset();
                    ms0 = -System.currentTimeMillis();
                    nt0 = -System.nanoTime();
                    b.execute(l);
                    nt0 += System.nanoTime();
                    ms0 += System.currentTimeMillis();
                    ms += ms0;
                    nt += nt0;
                }
            }
            if (ms < minMs) {
                System.out.printf(patternNxIntervall, b.getName(), ln, mmin, mmax, ms, ms / ln,
                        nt, (nt / ln));
            } else {
                System.out.printf(patternNxIntervallWNT, b.getName(), ln, mmin, mmax, ms,
                        ms / ln, "...", "...");
            }
        }
    }
 
    /*
     * F&uuml;hrt Benchmarks zu einem gegebenen Wert aus. Der Benchmark wird zurerst
     * zur&uuml;ckgesetzt und zu dem Wert val vorbereitet.
     *
     * @param b Benchmark.
     * @param n Iterationen.
     * @param minMs minimale Millesekunden, bei denen Nanosekunden angezeigt werden.
     * @param val Wert.
     */
    private void execute(Bench b, long[] n, long minMs, long val) {
        for (int i = 0; i < n.length; i++) {
            long ln = n[i];
            long ms = 0;
            long nt = 0;
            long ms0 = 0;
            long nt0 = 0;
            for (int ii = 0; ii < ln; ii++) {
                b.reset();
                b.prepare(val);
                ms0 = -System.currentTimeMillis();
                nt0 = -System.nanoTime();
                b.execute(val);
                nt0 += System.nanoTime();
                ms0 += System.currentTimeMillis();
                ms += ms0;
                nt += nt0;
            }
            if (ms < minMs) {
                System.out.printf(patternNxM, b.getName(), ln, val, ms, ms / ln, nt, (nt / ln));
            } else {
                System.out.printf(patternNxMWNT, b.getName(), ln, val, ms, ms / ln, "...",
                        "...");
            }
        }
    }
}

Ein kleines Beispiel zum Traversieren einer Liste (NxM):

     Bench b0 = new Bench() {
            List<String> l = null;
 
            @Override
            public String getName() {
                return "Traverse";
            }
 
            @Override
            public void reset() {
                l = new ArrayList<String>();
            }
 
            @Override
            public void prepare(long lm) {
                for (int i = 0; i < lm; i++) {
                    l.add(String.valueOf(i));
                }
            }
 
            @Override
            public void execute(long lm) {
                for (int i = 0, n = l.size(); i < n; i++) {
                    String s = l.get(i);
                }
            }
        };

Benutzung:

...
        QuickBench.benchNxM(10, 50000, new long[] { 10, 100, 1000 }, new long[] { 10000, 100000 },
                new Bench[] { b0 });
...

Als Ergebnis erhält man folgendes:

+---------------------------+----------+------------+----------+----------+-----------------+-----------------+
| Bench                     |   Rounds |    Objects |       ms | ms (avg) |              nt |        nt (avg) |
+---------------------------+----------+------------+----------+----------+-----------------+-----------------+
| Traverse ArrayList For    |       10 |     10.000 |        0 |        0 |       1.945.638 |         194.563 |
| Traverse ArrayList For    |      100 |     10.000 |       16 |        0 |             ... |             ... |
| Traverse ArrayList For    |    1.000 |     10.000 |      171 |        0 |             ... |             ... |
| Traverse ArrayList For    |       10 |    100.000 |       16 |        1 |             ... |             ... |
| Traverse ArrayList For    |      100 |    100.000 |      109 |        1 |             ... |             ... |
| Traverse ArrayList For    |    1.000 |    100.000 |    1.170 |        1 |             ... |             ... |
+---------------------------+----------+------------+----------+----------+-----------------+-----------------+
java version 1.6.0_16
Java(TM) SE Runtime Environment(build 1.6.0_16-b01)
Java HotSpot(TM) Client VM(build 14.2-b01, mixed mode)
Windows Vista(6.0) x86
Max memory: 63MB

Legende:

Bench: Name des Benchmarks.
Rounds: Anzahl an Durchläufen.
Objects: Anzahl an verwendeteten Objekten.
ms: Gesamtzeitbedarf in Millisekunden.
ms (avg): Durchschnittlicher Zeitbedarf in Millisekunden.
nt: Gesamtzeitbedarf in Nanosekunden. Wenn ms > minMs wird hier nichts angezeigt.
nt (avg): Durchschnittlicher Zeitbedarf in Nanosekunden. Wenn ms > minMs wird hier nichts angezeigt.

Weitere Beispiele folgen...

Und zwar jetzt:

Zwei Beispiele zu NxIntervall (Fibonacci rekursiv und nicht-rekursiv):

    /* fibonacci -> recursive */
    static class FibBench_Recursive extends AbstractBench {
 
        private int fib(int a) {
            if (a == 1 || a == 2) {
                return 1;
            } else {
                return fib(a - 1) + fib(a - 2);
            }
        }
 
        @Override
        public String getName() {
            return "Fibonacci: recursive";
        }
 
        @Override
        public void prepare(long lm) {
            this.fib(1);
            this.fib(2);
            this.fib(3);
        }
 
        @Override
        public void execute(long lm) {
            this.fib((int) lm);
        }
    };
 
    /* fibonacci -> non-recursive */
    static class FibBench_NonRecursive extends AbstractBench {
 
        private long fib(int a) {
            long fib = 1;
            for (long fib1 = 1, fib2 = 1, i = 3; i <= a; i++) {
                fib = fib1 + fib2;
                fib1 = fib2;
                fib2 = fib;
            }
            return fib;
        }
 
        @Override
        public String getName() {
            return "Fibonacci: non-recursive";
        }
 
        @Override
        public void prepare(long lm) {
            this.fib(1);
            this.fib(2);
            this.fib(3);
        }
 
        @Override
        public void execute(long lm) {
            this.fib((int) lm);
        }
    };

Aufruf wie folgt:

    public static void main(String[] args) {
        QuickBench.benchNxIntervall(10, 50000, new long[] { 10, 100, 1000}, 1, 25,
                new Bench[]{new FibBench_Recursive(), new FibBench_NonRecursive()});
    }

Als Ergebnis erhält man ungefähr folgende Ausgabe:

+---------------------------+----------+------------+------------+----------+----------+-----------------+-----------------+
| Bench                     |   Rounds |        min |        max |       ms | ms (avg) |              nt |        nt (avg) |
+---------------------------+----------+------------+------------+----------+----------+-----------------+-----------------+
| Fibonacci: recursive      |       10 |          1 |         25 |        0 |        0 |       8.312.579 |         831.257 |
| Fibonacci: recursive      |      100 |          1 |         25 |       93 |        0 |             ... |             ... |
| Fibonacci: recursive      |    1.000 |          1 |         25 |      812 |        0 |             ... |             ... |
| Fibonacci: non-recursive  |       10 |          1 |         25 |        0 |        0 |          25.911 |           2.591 |
| Fibonacci: non-recursive  |      100 |          1 |         25 |        0 |        0 |         241.581 |           2.415 |
| Fibonacci: non-recursive  |    1.000 |          1 |         25 |        0 |        0 |       1.974.832 |           1.974 |
+---------------------------+----------+------------+------------+----------+----------+-----------------+-----------------+
java version 1.6.0_16
Java(TM) SE Runtime Environment(build 1.6.0_16-b01)
Java HotSpot(TM) Client VM(build 14.2-b01, mixed mode)
Windows Vista(6.0) x86
Max memory: 63MB