【问题标题】:Average brightness with Clojure very slowClojure 的平均亮度非常慢
【发布时间】:2017-04-21 06:49:13
【问题描述】:

作为 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)
  )

虽然这确实给出了正确的结果,但速度非常慢。 20 兆像素的图像大约需要 10 到 20 秒。磁盘访问不是问题。从玩time 来看,罪魁祸首似乎是bytetoint 转换。仅将这个bytetoint 映射到字节数组会占用 8 GB 内存,并且不会在 REPL 中终止。

为什么会这样?对此我们能做些什么?

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

【问题讨论】:

    标签: java image clojure


    【解决方案1】:

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

    这种循环(计算和)在更直接的计算形式方面更好地实现,例如 clojure loop 构造,形式为:

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

    这是未经测试的,因此您可能需要对其进行调整,但它显示了一些内容:

    1. 使用类型提示数组访问
    2. 使用非常有效的直接循环
    3. 对实际循环使用“整数”(长)数学运算,并且只在末尾进行除法
    4. 使用未经检查的数学可以大大提高“紧密循环”中的性能

    编辑

    如果您确实需要挤出性能,您也可以使用其他可能表现更好的形式,例如具有内部可变状态(例如大小为 1 的长向量)的 dotimes,但到那时,你还不如用java写一个小方法;)

    【讨论】:

      【解决方案2】:

      除了@shlomi 的回答:

      您还可以使用areduce 函数使其不那么冗长(并且可能更快一点):

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

      【讨论】:

        【解决方案3】:

        如果您想在 java 中真正快速地做到这一点,那么您可以使用这些选项(最好是全部使用):

        1. 使用 java wrapper for libjpeg-turbo 作为 jpeg 解压库 - 它比 ImageIO 快 30 倍...
        2. 不要从图像中的所有像素计算平均值,使用 1% 来计算 10% 的像素均匀分布在图像上(使用一些散列函数来选择伪随机像素 - 或者只是在 for 循环中跳转超过一个像素,具体取决于您想要击中多少像素)- 平均 以这种方式计算要快得多。您使用的像素越多,获得的结果就越准确 - 但如果您使用 5% 的均匀分布的选定像素,则足以获得非常好的结果。
        3. 多线程。
        4. 避免使用浮点计算,使用整数计算 - 浮点计算速度会慢 3-4 倍。 在可能的情况下
        5. 不要将所有图像都加载到内存中,因为图像通常会占用大量内存,这可能会导致垃圾收集器几乎无法工作并且您的应用程序因此运行缓慢, 最好在需要时加载它们,然后让它们被 GC-ed 那 - 逐步计算平均值

        关于负字节值... 不要把颜色值转成字节,直接转成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.
        

        【讨论】:

        • 不,我想在 Clojure 中进行。 (1) ImageIO 需要大约 1 % 的计算时间,因此加速 30 倍(该库无法通过 SATA 限制实现)将无济于事。 (2) 显而易见,但在 Clojure 中实现起来似乎并不容易。也应该是不必要的。 (3) 多线程并没有让任何事情变得更快,只是更加分散。
        • 为什么要避免浮点计算?否则 Clojure 会使用更慢且更不实用(此处)的有理数。
        • 原始问题没有提到在clojure中必须这样做。
        【解决方案4】:

        除了上述有用的信息之外,您可能对 HipHip 库感兴趣,该库设计用于从 Clojure 操作原始值数组:https://github.com/plumatic/hiphip

        这是自述文件中关于计算原始数组的均值和标准差的示例:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-07-02
          • 1970-01-01
          • 2012-08-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多