【问题标题】:How can I fill this buffer faster in clojure如何在 clojure 中更快地填充此缓冲区
【发布时间】:2016-03-24 19:08:07
【问题描述】:

我正在尝试创建一个函数,该函数将采用 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 的图块集需要 10 秒,这是完全不能接受的。我正试图让这个运行在不到一秒的时间内完成。

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

我不妨提一下,这些时间是使用 REPL 记录的。

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

【问题讨论】:

  • 您可以使用clojure.core/time 来衡量您的代码中哪些部分花费的时间最多。
  • 我已经做到了。从我的帖子中:“请注意,一直占用的部分是循环。”。
  • Criterium 是一种更好的基准测试方法。一般来说,mapreducefilter 可能是比loop/recur 更好的速度选择和更惯用的选择。
  • 我认为 map、reduce 和 filter 实际上会比尾调用优化循环更糟糕,因为构建惰性序列的开销,不是这样吗?
  • 你为什么要用 byte[] 来做这个? ByteBuffer 是专门存在的,因此您不必做太多间接摆弄堆上的东西。在函数开始时分配一个大小正确的 ByteBuffer,然后多次调用.put,而不是先创建一个 byte[] 然后再做一堆算术来计算出正确的偏移量!

标签: opengl optimization clojure lwjgl


【解决方案1】:

当你将*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 ...))

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

【讨论】:

  • 谢谢,所以问题不在于函数调用,而在于返回包装在对象中的字节的函数。我会记住这一点,并确保打开反射警告选项。谢谢!
  • Clojure 的最新版本确实支持byte and bytes hints。你也可以使用bytes casting
  • @kawas44 是的,Clojure 确实支持 bytebytes 提示,但问题是对于原始类型,Clojure 函数只能返回 longdouble。它们不支持 intbyte 或其他原始返回类型(编译器消息明确指出:java.lang.IllegalArgumentException: Only long and double primitives are supported)。
【解决方案2】:

我通过将子字节转换为宏,将时间从 10s 减少到 173ms:

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

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

我觉得这很有趣,我不认为函数调用在 Clojure 中会如此低效。另外,我认为编译器正在为我执行内联优化。

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

【讨论】:

    猜你喜欢
    • 2021-12-22
    • 2017-01-05
    • 2011-02-20
    • 2018-02-07
    • 1970-01-01
    • 1970-01-01
    • 2012-02-04
    • 2013-02-21
    • 1970-01-01
    相关资源
    最近更新 更多