Clojure视频数据性能问题
我正在写一些代码来生成和处理大量的视频数据。起初,我只打算处理随机数据 我的技术是将像素视为R、G、B、整数值的映射,将视频帧视为这些像素映射的向量,并将跨时间的视频视为这些像素映射向量的向量。我已经编写了三个函数,它们可以可靠地实现这一点,但在扩展时会遇到性能问题Clojure视频数据性能问题,clojure,jvm,video-processing,Clojure,Jvm,Video Processing,我正在写一些代码来生成和处理大量的视频数据。起初,我只打算处理随机数据 我的技术是将像素视为R、G、B、整数值的映射,将视频帧视为这些像素映射的向量,并将跨时间的视频视为这些像素映射向量的向量。我已经编写了三个函数,它们可以可靠地实现这一点,但在扩展时会遇到性能问题 (defn generateFrameOfRandomVideoData "Generates a frame of video data which is a vector of maps of pixel values."
(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)
现在我已经说服了自己,也希望你相信我的算法不仅仅是难以解决的,我想我有一些连贯的问题:
java.lang.integer
与java中的相同,并且每个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"
每个框架都将非常大:
(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"
为什么算法在大量帧的时间和内存方面停滞不前?Clojure做了什么来占用这么多内存
每像素存储一个哈希图会很快加起来,如果每个1920x1080帧都是232 MB,那么每4帧就有1 GB。我不认为这是对Culjule-这是一种昂贵的任何语言的存储方案。我会考虑一些事情:
- 更有效地存储单个像素值,例如,将每个像素表示为压缩为单个32位整数的四个无符号字节。如果有这么多数据点,且所有数据点都位于同一结构中,则开放哈希映射可能是空间效率最低的结构之一
由于地图形状定义良好,因此可以使用记录来节省空间并具有类似地图的语义:
四个基本整数数组仅略大于单个(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
对象:整数
类似的向量是10倍大:(mem/measure (int-array (range 4))) => "32 B"
您可以尝试字节数组,但JVM没有无符号字节原语:(mem/measure [(int 0) (int 1) (int 2) (int 3)]) => "320 B"
(mem/measure (byte-array 4)) => "24 B"
- 有很多不可变的数据结构在发生变化,每个像素和帧都被连接到一个现有的向量上,而Clojure的持久数据结构并不是“免费”的。更有效的方法是使用,但是
- 您需要将所有这些帧存储在内存中吗?如果不需要,您可以在不保存所有帧的情况下延迟流式处理这些帧。如果您必须将它们构建到一个大型的、已实现的集合中,可以使用瞬态、JVM阵列等
本例惰性地分析无限序列的每一帧,并获取前60个结果;分析的帧/像素数据在运行时被垃圾收集,因此不会耗尽内存(但GC会很忙)(defn gen-frame [num-pixels] (repeatedly num-pixels #(->Pixel (rand-int 256) (rand-int 256) (rand-int 256) (rand-int 256)))) (defn frame-op [frame] ;; not very interesting for random pixels (let [num-pixels (count frame) avg #(double (/ (apply + (map % frame)) num-pixels))] (->Pixel (avg :r) (avg :g) (avg :b) (avg :a)))) (time (->> (repeatedly #(gen-frame (* 1920 1080))) (map frame-op) (take 60) (doall))) "Elapsed time: 240527.803662 msecs" => (#sandbox.core.Pixel{:r 127.4540152391975, :g 127.4542722800926, :b 127.3754962384259, :a 127.4886294367284} #sandbox.core.Pixel{:r 127.4727488425926, :g 127.4447955246914, :b 127.4472164351852, :a 127.4626080246914} ...
如果你需要从Taylor Wood的答案中得到进一步的加速,考虑进一步压缩你的存储空间。 如果您只需按99,Clojure会将其存储为
java.lang.Long
,每个数字占用64字节。使用java.lang.Integer
会将其减半,每个数字占用32字节
但我们还有进一步的优化空间!您生成的数字介于0和255之间,这意味着您需要log2(256)=8位用于每个数字的存储。然后,我们可以将所有三个RGB值放入一个java.lang.Integer
我从下面开始。这种方法的功劳是。如果你想调整更多,你可以尝试避免我使用rem
和quot
而改为使用位摆弄。内存将是相同的,但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]
(defn可编码?[i]
(和(nat int?i)
(int
“将RGB值存储在单个整数中!”
[[RGB]]
(do(断言(可编码int?r))
(断言(可编码int?g))
(断言(可编码int?b)))
(int)
(+(*256 r)
(*256克)
b) ))
(定义int->rbg[i]
[(rem(QUOTE i(*256))256)
(rem(QUOTE i 256)256)
(rem i 256)])
让我们试着存储99101255!
(def c[99 101 255])
(rgb->INTC)
;; => 6514175
(类型(rgb->INTC))
;;=>java.lang.Integer
(->c rgb->int int->rbg)
;; => [99 101 255]
这都是很好的分析,但我认为最后一个问题没有得到足够的重视。即使是这种非常臃肿的存储格式,如果您懒散地处理它,也是完全可以管理的,对于大多数应用程序来说,懒散也没问题:将每个帧写入磁盘,然后扔掉,或者其他什么。总体回答很好。您检查了内存多少了吗如果为每个像素使用一条记录,则使用y?可能是对您的答案的一个很好的补充。T
(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]