2016-03-24 12 views
1

Ich versuche, eine Funktion, die ein BufferedImage nehmen und einen ByteBuffer zurückgeben kann ich dann als OpenGL-Textur verwenden. Um das zu tun, habe ich gelernt, dass ich einige Byte-Verschiebungen machen muss, die für meine Frage nicht relevant sind. Es hat damit zu tun, dass die BufferedImage-Werte ARGB und OpenGL RGBA sind.Wie kann ich diesen Puffer in Clojure schneller füllen

Die Funktion I (aus Java) ist dies eine zu implementieren bin versucht:

public static ByteBuffer toByteBuffer(BufferedImage img){ 
    byte[] byteArray = new byte[img.getWidth()*img.getHeight()*4]; 
    for(int i = 0; i < img.getWidth()*img.getHeight(); i++){ 
     int value = img.getRGB(i%img.getWidth(), (i-(i%img.getWidth()))/img.getWidth()); 
     byteArray[i*4] = (byte) ((value<<8)>>24); 
     byteArray[i*4+1] = (byte) ((value<<16)>>24); 
     byteArray[i*4+2] = (byte) ((value<<24)>>24); 
     byteArray[i*4+3] = (byte) (value>>24); 
    } 
    return (ByteBuffer) ByteBuffer.allocateDirect(byteArray.length).put(byteArray).flip(); 
} 

Und das ist mein Versuch mit clojure:

(defn sub-byte [^long b ^long x] 
    (unchecked-byte (-> x 
    (bit-shift-left (* 8 b)) 
    (bit-shift-right 24)))) 


(defn bufferedimage->bytebuffer [^BufferedImage img] 
    (binding [*unchecked-math* true] 
    (let [w (.getWidth img) 
      h (.getHeight img) 
      ^bytes arr (make-array Byte/TYPE (* 4 w h))] 
     (loop [i 0] 
      (let [img-i (mod i w) 
       img-j (quot i w) 
       value (.getRGB img img-i img-j)] 
      (aset arr (* i 4)  (sub-byte 1 value)) 
      (aset arr (+ 1 (* i 4)) (sub-byte 2 value)) 
      (aset arr (+ 2 (* i 4)) (sub-byte 3 value)) 
      (aset arr (+ 3 (* i 4)) (sub-byte 0 value)) 
      (when (< (+ i 1) (* w h)) (recur (+ i 1))) 
      )) 
     (cast ByteBuffer (-> (ByteBuffer/allocateDirect (count arr)) 
          (.put arr) 
          (.flip)))))) 

Dies dauert 10 Sekunden, um eine 512 zu laden * 512 Tileset, was völlig inakzeptabel ist. Ich versuche, das in weniger als einer Sekunde zu machen.

Beachten Sie, dass der Teil, der die ganze Zeit dauert, die Schleife ist.

Ich könnte auch erwähnen, dass diese Zeiten mit der REPL genommen werden.

Beachten Sie auch, dass ich weiß, dass ich Java für die performance-kritischen Teile meines Codes verwenden kann, also ist dies eher eine theoretische Frage, damit ich lernen kann, wie ich meinen Clojure-Code optimieren kann.

+0

Sie könnten 'clojure.core/time' verwenden, um zu messen, welche Teile Ihres Codes die meiste Zeit benötigen. –

+1

habe ich schon getan. Von meinem Beitrag: "Beachten Sie, dass der Teil, der die ganze Zeit beansprucht, die Schleife ist." – Setzer22

+0

[Criterium] (https://github.com/hugoduncan/criterium) ist eine bessere Möglichkeit zum Benchmarking. Im Allgemeinen sind "map", "reduce" und "filter" wahrscheinlich bessere Wahlmöglichkeiten für Geschwindigkeit und idiomatischer als "loop/recur". – jmargolisvt

Antwort

2

Das Problem mit Lösung mit der Funktion aufgedeckt wird, wenn Sie gesetzt *warn-on-reflection* zu true:

(set! *warn-on-reflection* true) 

Wenn Sie Ihren Code laden, wird der Compiler Ihnen sagen, dass Ihre sub-byte Funktion Object zurück und es kann nicht statisch eine passende lösen Methode.

Reflection warning, web_app/so.clj:26:11 - call to static method aset on clojure.lang.RT can't be resolved (argument types: [B, int, java.lang.Object). 

Leider kann man nicht eine Art Hinweis für byte Rückgabewert auf Ihrer Funktion als nur long und double Primitive als Rückgabetypen werden unterstützt:

(defn sub-byte ^byte [^long b ^long x] 
    (unchecked-byte (-> x 
         (bit-shift-left (* 8 b)) 
         (bit-shift-right 24)))) 

CompilerException java.lang.IllegalArgumentException: Only long and double primitives are supported, compiling:(web_app/so.clj:7:1) 

Sie könnten versuchen, ^long als die Rückkehr zu deuten Geben Sie dann aber den angedeuteten Ergebnistyp nicht ein, was Ihr Funktionskörper zurückgibt (byte):

(defn sub-byte ^long [^long b ^long x] 
    (unchecked-byte (-> x 
         (bit-shift-left (* 8 b)) 
         (bit-shift-right 24)))) 

CompilerException java.lang.IllegalArgumentException: Mismatched primitive return, expected: long, had: byte, compiling:(web_app/so.clj:7:1) 

können Sie haben aber Ihre Funktion long zurückzukehren, aber dann muss man es überall mit unchecked-byte wickelt - auf diese Weise beseitigen Sie alle Reflexionen Warnungen:

(defn sub-byte ^long [^long b ^long x] 
    (-> x 
     (bit-shift-left (* 8 b)) 
     (bit-shift-right 24)))) 

(unchecked-byte (sub-byte ...)) 

Eine andere Lösung ist Makro zu verwenden, wie Sie herausgefunden bereits die Vermeiden Sie Probleme mit Funktionsaufrufen und deren Rückgabetypen.

+0

Vielen Dank, das Problem lag also nicht in Funktionsaufrufen, sondern in der Rückgabe des in ein Objekt eingepackten Bytes. Ich werde das im Hinterkopf behalten und sicher sein, dass diese Warn-on-Reflection-Option eingeschaltet wird. Vielen Dank! – Setzer22

+0

Aktuelle Versionen von Clojure unterstützen [Byte- und Byte-Hinweise] (http://clojure.org/reference/java_interop#TypeAliases). Sie könnten auch [bytes casting] (http://clojuredocs.org/clojure.core/bytes) verwenden. – kawas44

+0

@ kawas44 Ja, Clojure unterstützt 'byte' und' bytes' Hinweise, aber das Problem ist, dass Clojure Funktionen nur 'zurückgeben können lang "oder" doppelt "für die primitiven Typen. Sie unterstützen nicht 'int' oder' byte' oder andere primitive Rückgabetypen (da die Compiler-Nachricht eindeutig besagt: 'java.lang.IllegalArgumentException: Nur lange und doppelte Primitive werden unterstützt'). –

0

I reduziert die Zeit von 10s bis 173ms durch Unter Byte in ein Makro konvertieren:

(defmacro sub-byte [b x] 
    `(unchecked-byte (-> ~x 
    (bit-shift-left (* 8 ~b)) 
    (bit-shift-right 24)))) 

Es scheint, dass die Performance-Probleme mit allen Funktionsaufrufe zu tun hatte.

Ich finde es ziemlich faszinierend, aber ich dachte nicht, dass Funktionsaufrufe in Clojure so ineffizient wären. Außerdem dachte ich, der Compiler führe Optimierungen unter der Haube für mich durch.

Obwohl ich das "Was" herausgefunden habe, ist mir das "Warum" nicht bewusst, also werde ich eine Antwort akzeptieren, die erklärt, was hier vorgeht.

Verwandte Themen