clojure - Clojure 视频数据性能问题
问题描述
我正在编写一些代码来生成和处理大量视频数据。起初我只打算使用随机数据。
我的技术是将像素视为 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)。
既然我已经说服自己并希望你相信我的算法不仅仅是难以处理的,我想我有一些连贯的问题:
Clojure 中的整数位占用多少内存?我似乎无法在任何地方找到此信息。
存储绑定到映射键的整数会导致什么样的开销?就内存而言,它是否比将它们保存在向量中更昂贵?
为什么算法在大量帧的时间和内存方面陷入困境?Clojure 做了什么来占用这么多内存?
谢谢!
解决方案
Clojure 中的整数位占用多少内存?
16 字节,根据clj-memory-meter:
(mem/measure (rand-int 256))
=> "16 B"
只有 4 个字节用于表示 32 位整数值,但java.lang.Integer
Clojure 中的 a 与 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,则存储每个像素的哈希图将很快增加。我认为这不是 Clojure 特有的——对于任何语言来说,这都是一种昂贵的存储方案。我会考虑几件事:
更有效地存储单个像素值,例如将每个像素表示为打包成单个 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
四个原始整数数组仅比单个
Integer
对象大一点:(mem/measure (int-array (range 4))) => "32 B"
一个相似的向量要大 10 倍:
(mem/measure [(int 0) (int 1) (int 2) (int 3)]) => "320 B"
您可以尝试一个字节数组,但 JVM 没有无符号字节原语:
(mem/measure (byte-array 4)) => "24 B"
发生了很多不可变数据结构的变化,其中每个像素和帧都被
conj
'd 到一个现有的向量上,而 Clojure 的持久数据结构并不是“免费”的。一种更有效的方法是使用瞬态,但是......您是否需要将所有这些帧存储在内存中?如果没有,您可以懒惰地流式传输这些内容,而无需全部保存。如果您必须将它们构建成一个大型的、已实现的集合,则可以使用瞬态、JVM 数组等。
(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} ...
这个例子是懒惰地分析无限序列的每一帧,并取前 60 个结果;分析的帧/像素数据在运行时会被垃圾收集,因此它不会耗尽内存(但 GC 会很忙)。
这些参数解析为 O(n)。
有时,大常数很重要!
推荐阅读
- arrays - 数组之间交换值的问题
- node.js - http请求上的Ubuntu 20.04服务器超时
- php - 类中匿名函数的内存泄漏?
- javascript - 根据从多选下拉列表中选择的价格数据总和
- c# - 一个看似简单的 RaycastALL 统一问题我想不通
- python - 虽然循环“继续”在尝试中不起作用,但最终除外
- next.js - 尝试将 Nextjs 部署到 Vercel 时出现错误
- windows - 我刚买了一台装有 Windows 10 的新电脑,每次尝试打开 GitBash 时都会出现错误消息
- python - 在 Keras 中合并多个模型(张量流)
- java - 本地依赖 Exoplayer 模块