JRE7 langsamer als JRE6 - was läuft schief?

fisch@namenssuche

BIOS-Overclocker(in)
JRE7 langsamer als JRE6 - was läuft schief?

Hi zusammen,

ich bin auf ein mir unerklärliches Phänomen gestoßen:
Bei einem selbst geschriebenen Benchmark (dieser berechnet das Mandelbrot-Fraktal und Ausschnitte davon), geschrieben mit den Mitteln von Java6, erreiche ich unter der JRE6 (z.B. u41) recht passable Laufzeiten, die unter der JRE7 (u45) jedoch förmlich explodieren. Auf meinem Rechner zu Hause mit einem AMD FX 6300 ist es der Faktor 6, mit die JRE6 flotter unterwegs ist.

Kontrolliert habe ich:
- das Ergebnis (dieses ist in jedem Fall völlig korrekt und identisch)
- die Kernskalierung (diese ist bei beiden Fällen äußerst gut)
- Google :ugly:, was in jedem Fall das Gegenteil behauptet -> die JRE7 soll performanter sein als die JRE6
- dieses Phänomen nicht nur auf den heimischen PC's, sondern auch auf dem Arbeitsrechner -> vergleichbares Verhalten

"Besonderheiten" des Programmes: Es ist sehr FPU-lastig (ich rechne mit double-Variablen), für das Multithreading nutze ich eine (nicht anonyme!) innere Klasse (abgeleitet von Thread / Runnable). Die kann ein wenig mehr "Overhead" erzeugen, als eine externe, "normale" Threadklasse - letztere würde aber den Code massiv aufblähen und der Overhead sollte nicht nur in der JRE7 zu spüren sein.

Ich kann bei Bedarf gerne einen Teil des Quellcodes hochladen, bin jedoch noch nicht so ganz mit dem Programm fertig - unfertigen Code gebe ich nur ungern einfach so "heraus" ;).

Ist jemandem so ein Performanceproblem bekannt?
Hat jemand einen Tipp, ob dieses Verhalten an "mir" (meinem Code) liegt oder ob die JRE7 wegen irgendwelchen Security-bugfixes in manchen Fällen einfach massiv inperformanter als der Vorgänger geworden ist?

Gruß
 
Zuletzt bearbeitet:
AW: JRE7 langsamer als JRE6 - was läuft schief?

Hi,

ich bin kein Java Experte, aber habe ein paar Vermutungen. Meine Vermutungen gehen in Richtung der Art von Phänomenen, auf die man auch als guter Programmierer kaum kommt. Meist hat das mit dem internen Aufbaue der Hardware zu tun und wie moderne Prozessoren funktionieren.
Zu diesen Phänomenen zähle ich z.B. Branch Prediction. Dazu sei ein sehr guter Stackoverflow Thread zu empfehlen: java - Why is processing a sorted array faster than an unsorted array? - Stack Overflow
Außerdem dazu zähle ich das Optimieren auf Cache Lokalität, um Cache misses zu vermeiden. Klassisches beispiel hierbei ist Matrizenmultiplikation von sehr großen Matrizen, wo ein etwas anderer Quellcode bereits einen Performanceunterschied im Stellen bereiche 10-fach macht. Grundsätzlich gehts bei der Optimierung nur darum, Speicherzugriffe, die zeitlich aufeinander folgen, möglichst in einem kleinen Bereiche im Speicher zu halten. So ist die Wahrscheinlichkeit viel größer, dass die Daten noch in einem Cache der CPU liegen und nicht der "langsame" RAM angesprochen werden muss.

Aber ich vermute auch nur (beides trotzdem sehr interessante, lesenswerte Themen). Es wäre wohl hilfreich, wenn du Quellcode postest. Ich mag auch nicht unfertigen Code rauszugeben, aber es würde uns bei der Frage helfen. Es genügt ja der performance-kritische Abschnitt.

Grüße
Lukas
 
AW: JRE7 langsamer als JRE6 - was läuft schief?

Danke für die Anhaltspunkte :daumen:.
Die Prinzipien sind mir bekannt (beide Themen wurden bei mir letztes Semester in der Vorlesung Rechnerarchitektur recht gut abgedeckt).

Ich hab' mich bemüht möglichst viel Performance zu erreichen ohne unleserlichen und aufgeblähten Code zu produzieren.
Der Speicherverbrauch des Programmes ist verhältnismäßig gering, typischerweise sind während dem ersten Fraktal bei 9 Millionen Pixel/Punkte keine 10 MB :D.

Besonders performance-kritisch ist die Berechnung der einzelnen Punkte, da diese i.d.R. mehrere Millionen mal durchgeführt wird.
Hier habe ich sämtliche häufig genutzten Methoden auf Einzeiler heruntergebrettert, die afaik eh von normalen Compilern wegoptimiert werden dürften.

Klasse, welche die Koordination der Berechnung und der Threads (innere Klasse) übernimmt:
Code:
package fractBench;

import static java.lang.Math.sqrt;
import static java.lang.Math.pow;

/**
* Provides methods to compute and visualize fractals.
* @author Fabian [Nachname entfernt]
* @version 2013-12-03
*/

public class FractBenchCoordinator{
   private final int computingIterations;
   private final int height;
   private final int width;
   private final double lowRealBorder;
   private final double upperRealBorder;
   private final double lowImaginaryBorder;
   private final double upperImaginaryBorder;
   private double realStepSize;
   private double imaginaryStepSize;
   private int [] dataBuffer;
   private int currentLine;

   /**
    * Computes the Mandelbrot-Fractal single-threaded using the outer-class-variables and methods.
    */
   private class fractBenchThread extends Thread{
       public void run(){
           MandelbrotPointComputer worker = new MandelbrotPointComputer(computingIterations);
           int [] lineBuffer = new int [width];
           int nextLine = getNextLine();
           while(nextLine < height){
               computeLine(nextLine, lineBuffer, worker);
               nextLine = getNextLine();
           }
       }
   }
   
   public FractBenchCoordinator(final double lowRealBorderInit, final double upperRealBorderInit, 
           final double lowImaginaryBorderInit, final double upperImaginaryBorderInit, 
           final int precisionInit, final int heightInit, final int widthInit){
       lowRealBorder = lowRealBorderInit;
       upperRealBorder = upperRealBorderInit;
       lowImaginaryBorder = lowImaginaryBorderInit;
       upperImaginaryBorder = upperImaginaryBorderInit;
       computingIterations = precisionInit;
       height = heightInit;
       width = widthInit;
       initAdditionalValues();
   }  
   
   /**
    * Computes the Mandelbrot-Fractal multi-threaded using the constructor-defined parameters.
    * @param numberOfThreads Integer, defines the number of threads to use
    */
   public void computeMandelbrotFractalMultiThreaded(final int numberOfThreads){
       Thread[] threadStore = new Thread[numberOfThreads];
       for(int i = 0; i < numberOfThreads; i++){
           threadStore[i]= new fractBenchThread();
       }
       for(Thread t: threadStore){
           t.start();
       }
       for(Thread t: threadStore){
           try{
               t.join(); 
           }catch(InterruptedException iox){
               iox.printStackTrace();
           }
       }
   }   

    /**
     * Creates a picture of the previously computed fractal-data and writes it into the local application folder.
     */
   public void createPictureOfResult(final String fileName){
       FractalPictureCreator creator = new FractalPictureCreator();
       creator.createBlueYellowRedPicture(fileName, dataBuffer, height, width, computingIterations);
   }
   
   /**
    * Initializes some variables that don't need to be set directly within the constructor.
    * */
   private void initAdditionalValues(){
       realStepSize = getDistance(lowRealBorder,upperRealBorder) / (width - 1);
       imaginaryStepSize = getDistance(upperImaginaryBorder,lowImaginaryBorder) / (height - 1);
       currentLine = 0;
       dataBuffer = new int [height*width];
   }
   
   /**
    * Computes the distance between two coordinates.
    * */
   private double getDistance(final double a, final double b) {
       if(a != b && (a != 0 || b != 0))
       {
           return sqrt(pow((b-a),2));
       }
       return 0;
   }

   /**
    * Returns the next line of a fractal-picture that has to be computed. Threadsafe.
    * */
   private synchronized int getNextLine(){
       return currentLine++;
   }
   
   /**
    * Computes one single line of a fractal-picture.
    * */
   private void computeLine(final int currentLine, final int [] lineBuffer, final MandelbrotPointComputer worker){
       final double yCoordinate = upperImaginaryBorder - currentLine * imaginaryStepSize;
       for(int positionInLine = 0; positionInLine < width; positionInLine++){
           worker.setMandelbrotPointCoordinates(lowRealBorder + positionInLine * realStepSize, yCoordinate);
           worker.computePoint();
           lineBuffer[positionInLine]= worker.getNumberOfIterations();
       }
       writeLineToSharedBuffer(currentLine, lineBuffer);
   }
   
   /**
    * Writes the computed line to a global shared buffer. Threadsafe.
    * */
   private void writeLineToSharedBuffer(final int currentLine, final int [] lineBuffer){
       int offset = currentLine*width;
       synchronized (dataBuffer){
           System.arraycopy(lineBuffer, 0, dataBuffer, offset, width);
       }
   }
}

Klasse, welche einen einzelnen Punkt berechnet:
Code:
package fractBench;

import static java.lang.Math.pow;
import static java.lang.Math.sqrt;

/**
 * Provides methods to compute a single point of the Mandelbrot-Fractal.
 * @author Fabian [Nachname entfernt]
 * @version 2013-12-03
 */
public class MandelbrotPointComputer{
    private double realInit;
    private double currentReal;
    private double imaginaryInit;
    private double currentImaginary;
    private final int maximumIterations;
    private int currentIterations;
    private double complex;
    private final static double EPSILON = 4.000000000000001;
    
    public MandelbrotPointComputer(final int maximumIterationSteps){
        maximumIterations = maximumIterationSteps;
    }

    public void setMandelbrotPointCoordinates(final double real, final double imaginary){
        realInit = real;
        imaginaryInit = imaginary;
        currentIterations = 0;
    }
    
    public void computePoint(){
        currentReal = realInit;
        currentImaginary = imaginaryInit;
        complex = getNextComplex(currentReal, currentImaginary);
        currentIterations++;
        while(currentIterations < maximumIterations && isWithinFractal(complex)){
            double currentRealBAK = currentReal;
            currentReal = getNextReal(currentReal, currentImaginary);
            currentImaginary = getNextImaginary(currentRealBAK, currentImaginary);
            complex = getNextComplex(currentReal, currentImaginary);
            currentIterations++;
        }
        if(isWithinFractal(complex))    //either the point is still convergent and might be part of the fractal
            complex = sqrt(complex);    //so its "final" absolute value has to be calculated like this
        else
            complex = Double.MAX_VALUE; //or it is divergent and its absolute value will head to infinity
    }

    public boolean resultIsPartOfTheMandelbrotFractal(){
        if(currentIterations == 0)
            return false;
        return isWithinFractal(complex);
    }
    
    public double getComputedResult(){
        if(currentIterations == 0)
            return 0;
        return complex;
    }
    
    public int getNumberOfIterations(){
        return currentIterations;
    }
   
    private double getNextComplex(final double currentReal,final double currentImaginary){
       return pow(currentReal, 2) + pow(currentImaginary, 2);
    }

    private double getNextReal(final double currentReal, final double currentImaginary){
        return pow(currentReal, 2) - pow(currentImaginary, 2) + realInit;
    }

    private double getNextImaginary(final double currentReal, final double currentImaginary){
        return 2 * currentReal * currentImaginary + imaginaryInit;
    }
    
    private boolean isWithinFractal(final double value) {
        return value < EPSILON;
    }
}
 
Zuletzt bearbeitet:
Zurück