如何在clojure中更快地填充此缓冲区

时间:2016-03-24 19:08:07

标签: opengl optimization clojure lwjgl

我正在尝试创建一个将采用BufferedImage并返回ByteBuffer的函数,然后我可以将其用作OpenGL纹理。为此,我了解到我必须进行一些与我的问题无关的字节移位。它与BufferedImage值有关,即ARGB和OpenGL需要RGBA。

我正在尝试实现的功能(来自java)就是这个:

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();
}

这是我对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))))))

加载512 * 512 tileset需要10秒钟,这是完全不可接受的。我试图以不到一秒钟的速度运行。

请注意,一直占用的部分是循环。

我不妨提一下那些时间是使用REPL。

另外,请注意我很清楚我可以将java用于代码的性能关键部分,所以这更像是一个理论问题,所以我可以学习如何优化我的clojure代码。

2 个答案:

答案 0 :(得分:2)

当您将*warn-on-reflection*设置为true时,会显示解决方案使用功能的问题:

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

当您加载代码时,编译器会告诉您sub-byte函数返回Object并且无法静态解析匹配方法。

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).

不幸的是,您不能在函数上使用byte返回值的类型提示,因为只有longdouble基元支持作为返回类型:

(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)

您可能会尝试提示^long作为返回类型,但提示结果类型不是您的函数体返回的内容(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)

但是你可以让你的函数返回long,但是你必须用unchecked-byte将它包装到任何地方 - 这样你就可以消除所有的反射警告:

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

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

另一种解决方案是使用已经发现的宏,这将避免函数调用及其返回类型的任何问题。

答案 1 :(得分:0)

我通过将子字节转换为宏来将时间从10秒减少到173毫秒:

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

性能问题似乎与所有函数调用有关。

我发现它非常有趣,我不认为函数调用在Clojure中会如此低效。另外,我认为编译器正在为我进行内联优化。

虽然我发现了“什么”,但我并不知道“为什么”,所以我会接受一个解释正在发生的事情的答案,而不是我的答案。