Clojure视频数据性能问题

时间:2019-02-05 00:49:51

标签: clojure jvm video-processing

我正在编写一些代码来生成和处理大量视频数据。起初,我只打算处理随机数据。

我的技术是将像素视为R,G,B,A整数值的图,将视频帧视为这些像素图的向量,并将跨时间的视频视为这些向量的向量像素图。我编写了三个可靠地执行此功能的函数,但是在扩展它们时会遇到性能问题。

(defn generateFrameOfRandomVideoData
  "Generates a frame of video data which is a vector of maps of pixel values."
  [num-pixels-in-frame]
  (loop [num-pixels-in-frame num-pixels-in-frame
     pixels-added 0
     frame '[]]
(if (> num-pixels-in-frame pixels-added)
 (recur num-pixels-in-frame
        (inc pixels-added) 
        (conj frame (assoc '{} 
                           :r (rand-int 256)
                           :g (rand-int 256)
                           :b (rand-int 256)
                           :a (rand-int 256))))
 frame)))

(defn generateRandomVideoData
   "Generates a vector of frames of video data."
   [number-of-frames frame-height frame-width]
   (loop [number-of-frames number-of-frames
     frame-height frame-height
     frame-width frame-width
     frames '[]]
(if (> number-of-frames (count frames))
 (recur number-of-frames
        frame-height
        frame-width
        (conj frames (generateFrameOfRandomVideoData (* frame-height frame-width))))
 frames)))

 (defn generateRandomizedVideo
 "Generates video data based on the specified parameters."
 [number-of-frames frame-height frame-width]
    (assoc '{} 
     :number-of-frames number-of-frames
     :frame-height frame-height
     :frame-width frame-width
     :frames (generateRandomVideoData number-of-frames frame-height frame-width)))

调用此函数可使用这些功能生成60帧1920X1080p视频:

(generateRandomizedVideo 60 1920 1080)

当我运行此调用以生成10帧价值1920X1080p的视频时,该算法很快完成。当我称其为产生60帧视频时,它陷入沼泽,无法完成,并产生大量内存。我看到它占用了16GB的内存。

这对我来说真的没有任何意义。我的算法是O(帧数*(帧高*帧宽))。帧数为O(n),并且(帧的高度*帧的宽度恒定为O(高度*宽度)。这些参数解析为O(n)。

现在,我已经说服了自己,并希望您说我的算法并非简单易用,我想我有一些连贯的问题:

  1. Clojure中的整数以位为单位占用多少内存?我似乎在任何地方都找不到此信息。

  2. 存储绑定到映射键的整数会导致哪种开销?就内存而言,是否比仅将它们保存在向量中更昂贵?

  3. 为什么对于大量帧,该算法在时间和内存方面陷入困境? Clojure正在做什么以占用如此多的内存?

谢谢!

2 个答案:

答案 0 :(得分:7)

  

Clojure中的整数以位为单位占用多少内存?

16个字节,根据clj-memory-meter

(mem/measure (rand-int 256))
=> "16 B"

仅使用4个字节来表示32位整数,但是Clojure中的java.lang.Integer与Java中的相同,并且每个{ {1}}:

java.lang.Object
  

存储绑定到映射键的整数会导致哪种开销?就内存而言,是否比仅将它们保存在向量中更昂贵?

是的,在这种情况下几乎是原来的两倍:

(type (rand-int 256))
 => java.lang.Integer

每个框架都会很大:

(mem/measure [(rand-int 256) (rand-int 256) (rand-int 256) (rand-int 256)])
=> "320 B"
(mem/measure {:r (rand-int 256)
              :g (rand-int 256)
              :b (rand-int 256)
              :a (rand-int 256)})
=> "544 B"
  

为什么对于大量帧,该算法在时间和内存方面陷入困境? Clojure正在做什么以占用如此多的内存?

如果每个1920x1080帧约为232 MB,即每4帧约为1 GB,那么每个像素存储一个哈希映射将很快增加。我不认为这是Clojure特有的-对于任何语言,这都是一种昂贵的存储方案。我会考虑一些事情:

  • 更有效地存储各个像素值,例如将每个像素表示为四个无符号字节,打包成一个32位整数。当您拥有这么多数据点且都在同一结构中时,开放式哈希图可能是空间效率最低的结构之一。

    由于地图形状定义明确,因此可以使用记录来节省空间并具有类似地图的语义:

    (mem/measure
      (into [] (repeatedly (* 1920 1080)
                           (fn [] {:r (rand-int 256)
                                   :g (rand-int 256)
                                   :b (rand-int 256)
                                   :a (rand-int 256)}))))
     => "232.2 MB"
    

    4个原始整数数组仅比单个(defrecord Pixel [r g b a]) (mem/measure (->Pixel (rand-int 256) (rand-int 256) (rand-int 256) (rand-int 256))) => "112 B" ;; similar deftype is 96 B 对象大一点:

    Integer

    类似的向量大10倍:

    (mem/measure (int-array (range 4)))
    => "32 B"
    

    您可以尝试字节数组,但是JVM没有 unsigned 字节原语:

    (mem/measure [(int 0) (int 1) (int 2) (int 3)])
    => "320 B"
    
  • 发生了很多不可变的数据结构更改,每个像素和帧都(mem/measure (byte-array 4)) => "24 B" 添加到现有矢量上,而Clojure的持久性并不是“免费的”数据结构。一种更有效的方法是使用transients,但是...

  • 是否需要将所有这些帧存储在内存中?如果没有,您可以在不持有所有内容的情况下懒惰地播放这些内容。如果您必须将它们构建为一个大型的,实现的集合,则可以使用瞬态,JVM阵列等。

    conj

    此示例懒惰地分析无限序列的每个帧并获取前60个结果;分析的帧/像素数据在运行时会被垃圾收集,因此不会耗尽内存(但GC会很忙)。

  

这些参数解析为O(n)。

有时候,大常数很重要!

答案 1 :(得分:0)

如果您需要进一步提高@Taylor Wood的解决方案的速度,请考虑进一步压缩存储空间。

如果只按99,Clojure会将其存储为java.lang.Long,每个数字占用64个字节。使用java.lang.Integer会将其减少一半,每个数字占用32个字节。

但是我们还有进一步的优化空间!您生成的数字介于0到255之间,这意味着每个数字需要log2(256) = 8位用于存储。然后,我们可以将所有三个RGB值拟合到一个java.lang.Integer中!

我从下面开始。此方法的功劳归于mikera/imagez。如果您想进行更多调整,可以尝试避免使用我的remquot,而是摆弄一些东西。内存将相同,但CPU使用率将下降。

(defn encodable? [i]
  (and (nat-int? i)
       (< i 256)))

(defn rgb->int
  "Store an RGB value in a single integer!"
  [[r g b]]
  (do (assert (encodable-int? r))
      (assert (encodable-int? g))
      (assert (encodable-int? b)))
  (int
   (+ (* 256 256 r)
      (* 256 g)
      b)))

(defn int->rbg [i]
  [(rem (quot i (* 256 256)) 256)
   (rem (quot i 256) 256)
   (rem i 256)])

;; Let's try to store 99, 101, 255!

(def c [99 101 255])

(rgb->int c)
;; => 6514175

(type (rgb->int c))
;; => java.lang.Integer

(-> c rgb->int int->rbg)
;; => [99 101 255]