Clojure Вопросы о производительности видеоданных - PullRequest
0 голосов
/ 05 февраля 2019

Я пишу код для генерации и обработки больших объемов видеоданных.Сначала я намереваюсь работать только со случайными данными.

Моя техника состоит в том, чтобы рассматривать пиксель как карту целочисленных значений 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 кадров видео, оно застревает, не завершается и генерирует огромное количество памяти.Я смотрел, как это занимает 16 ГБ памяти.

Это на самом деле не имеет никакого смысла для меня.Мой алгоритм O (количество кадров * (высота кадра * ширина кадра)).Количество кадров O (n) и (высота кадра * ширина кадра постоянна при O (высота * ширина). Эти аргументы приводят к O (n).

Теперь, когда я убедил себя инадеюсь, вы понимаете, что мой алгоритм не просто неразрешим, я думаю, у меня есть несколько взаимосвязанных вопросов:

  1. Сколько памяти занимает целое число в Clojure в битах? Кажется, я не могу найти этоинформация где угодно.

  2. Какие накладные расходы вызывает хранение целых чисел, связанных с ключами карты? Является ли это более затратным с точки зрения памяти, чем просто хранение их в векторе?

  3. Почему алгоритм затягивается с точки зрения времени и памяти для большого количества кадров? Что Clojure делает для увеличения памяти?

Спасибо!

Ответы [ 2 ]

0 голосов
/ 07 февраля 2019

Если вам требуется дальнейшее ускорение по сравнению с ответом @Taylor Wood, рассмотрите возможность дальнейшего сжатия хранилища.

Если вы просто нажмете 99, Clojure сохранит это значение как java.lang.Long, взяв на себя64 байта на число.Использование java.lang.Integer сократит это пополам, занимая 32 байта на число.

Но у нас есть еще место для оптимизации!Вы генерируете числа от 0 до 255, что означает, что вам нужно log2(256) = 8 бит для хранения на номер.Затем мы могли бы поместить все три значения RGB в один java.lang.Integer!

, который я начал ниже.Кредиты для этого подхода идут к mikera / imagez .Если вам хочется больше подправить, вы можете попытаться избежать моего использования rem и quot и вместо этого пойти немного поиграть.Память будет такой же, но загрузка процессора снизится.

(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]
0 голосов
/ 05 февраля 2019

Сколько памяти занимает целое число в Clojure в битах?

16 байт, согласно clj-memory-meter :

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

Только 4 байта используются для представления 32-разрядного целого числа значение , но java.lang.Integer в Clojure такое же, как в 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"

Каждый кадр будет вполнеlarge:

(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 МБ, то есть ~ 1 ГБ на каждые 4 кадра.Я не думаю, что это специфично для 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 на существующий вектор, и это не «бесплатно» с постоянными структурами данных 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).

Большие константы имеют значение, иногда!

...