Java Clojure的平均亮度非常慢

Java Clojure的平均亮度非常慢,java,image,clojure,Java,Image,Clojure,作为Clojure的新手,我想计算(很多)jpg图像的平均亮度。为此,我使用Java中的ImageIO/read将图像加载到内存中,提取其后面的字节缓冲区并应用平均值 (defn brightness "Computes the average brightness of an image." [^File file] (-> file ImageIO/read .getRaster .getDataBuffer .getData byt

作为Clojure的新手,我想计算(很多)jpg图像的平均亮度。为此,我使用Java中的
ImageIO/read
将图像加载到内存中,提取其后面的字节缓冲区并应用平均值

(defn brightness
  "Computes the average brightness of an image."
  [^File file]
  (-> file
    ImageIO/read
    .getRaster
    .getDataBuffer
    .getData
    byteaverage))
这里是平均数

(defn byteaverage
  [numbers]
  (/ (float
     (->> numbers
        (map bytetoint)
        (apply +)))
     (count numbers))
  )
需要考虑到字节是用Java签名的,需要首先转换为足够大的整数

(defn bytetoint
   [b]
   (bit-and b 0xFF)
  )
虽然这确实给出了正确的结果,但速度非常慢。2000万像素的图像大约需要10到20秒。磁盘访问不是问题所在。从玩弄
时间
,罪魁祸首似乎是
字节点
转换。只需将这个
bytepoint
映射到字节数组就会消耗8GB的内存,并且不会在REPL中终止

为什么会这样?人们能做些什么呢


PS:我知道可以使用其他编程语言、库、多线程或更改算法。我的观点是,上面的Clojure代码应该快得多,我想了解它为什么不快。

如果您想在java中快速完成,那么您可以使用这些选项(最好使用所有选项):

  • 使用java wrapper for libjpeg turbo作为jpeg解压缩库-它比ImageIO快30倍
  • 不要计算图像中所有像素的平均值,请使用1%来 平均10%的像素均匀分布在图像上(使用一些哈希函数选择伪随机像素,或者只是跳转一个for循环,跳转超过一个像素,具体取决于您希望命中的像素数) 用这种方法计算要快得多。使用的像素越多,得到的结果就越精确——但如果使用5%的均匀分布的选定像素,就足以获得非常好的结果
  • 多线程
  • 避免使用浮点计算,使用整数计算-浮点计算的速度仅慢3-4倍。 可能的话
  • 不要将所有图像加载到内存中,因为图像通常会占用大量内存,这可能会导致垃圾收集器无法正常工作,而你的应用程序因此运行缓慢, 最好在需要时加载它们,并在需要后对其进行GC加载 即-以增量方式计算平均值
  • 至于负字节值。。。 不要将颜色值转换为字节,直接将其转换为int,如:

    int rgb = somePixelColor;
    int b = rgb & 0xFF;
    int g = (rgb>>8) & 0xFF;
    int r = (rgb>>16) & 0xFF;
    
    int sillyBrightness = (r + g + b)/3; // because each color should have a weight for calculating brightness, there are some models of that.
    

    您基本上是在一个非常紧密的循环中运行大量管道,例如装箱、转换、使用chuncked惰性序列等。。您从现代CPU中获得的许多好处都会立即消失;例如预加载缓存线、分支预测等

    这种循环(计算和)在更直接的计算形式上实现得更好,例如clojure
    loop
    construct,形式如下:

    (defn get-sum [^bytes data]
      (let [m (alength data)]
        (loop [idx 0 sum 0]
          (if (< idx m)
            (recur (inc idx) (unchecked-add sum (bit-and (aget data idx) 0xff)))
            (/ sum m)))))
    
    (定义获取和[^bytes数据]
    (设[m(长度数据)]
    (循环[idx 0和0]
    (如果(
    这是未经测试的,因此您可能需要对其进行调整,但它显示了以下几点:

  • 使用类型提示访问数组
  • 使用非常有效的直接循环
  • 对实际循环使用“整数”(长)数学,并仅在末尾除法
  • 使用未经检查的数学,在“紧密循环”中大大提高性能
  • 编辑
    您还可以使用其他形式,这些形式的性能可能会更好,例如具有内部可变状态的
    dotimes
    (例如大小为1的长向量),如果您确实需要压缩性能,那么您最好用java编写一个小方法;)

    除了@shlomi的回答之外:

    您还可以使用
    areduce
    函数使其更简洁(可能更快):

    (defn get-sum-2 [^bytes data]
      (/ (areduce data i res 0 
                  (unchecked-add res (bit-and (aget data i) 0xff)))
         (alength data)))
    

    除了上述良好信息外,您可能还对HipHip库感兴趣,该库设计用于操作Clojure中的基元值数组:

    以下是自述文件中关于计算基元数组的平均值和标准偏差的示例:

    (defn std-dev [xs]
      (let [mean (dbl/amean xs)
            square-diff-sum (dbl/asum [x xs] (Math/pow (- x mean) 2))]
        (/ square-diff-sum (dbl/alength xs))))
    
    (defn covariance [xs ys]
      (let [ys-mean (dbl/amean ys)
            xs-mean (dbl/amean xs)
            diff-sum (dbl/asum [x xs y ys] (* (- x xs-mean) (- y ys-mean)))]
        (/ diff-sum (dec (dbl/alength xs)))))
    
    (defn correlation [xs ys std-dev1 std-dev2]
      (/ (covariance xs ys) (* std-dev1 std-dev2)))
    

    不,我想在Clojure做。(1) ImageIO占用了大约1%的计算时间,因此将速度提高30倍(该库无法通过SATA限制实现)也无济于事。(2) 显而易见,但在Clojure中实现起来似乎并不那么容易。也应该是不必要的。(3) 多线程并没有使任何事情更快,只是更分散。为什么要避免浮点计算?否则,Clojure将使用更慢、更不实用的(此处)理性。原始问题并没有提到在Clojure中必须这样做。