Подтвердить что ты не робот

Написание функциональной и но функциональной библиотеки обработки изображений в Scala

Мы разрабатываем небольшую библиотеку обработки изображений для Scala (студенческий проект). Библиотека полностью функциональна (т.е. Не изменяет). Растр изображения хранится как Stream[Stream[Int]], чтобы использовать преимущества ленивой оценки с минимальными усилиями. Однако при выполнении нескольких операций над изображением куча заполняется и бросается OutOfMemoryError. (например, до 4 операций может быть выполнено на изображении JPEG размером 500 x 400, 35 кбайт до того, как куча JVM закончится вне пространства.)

Подходы, о которых мы думали, следующие:

  • Поворот с настройками JVM и увеличение размера кучи. (Мы не знаем, как это сделать в рамках IDEA - среды IDE, с которой мы работаем.)
  • Выбор другой структуры данных, чем Stream[Stream[Int]], которая больше подходит для задачи обработки изображений. (Опять же, мы не имеем большого представления о функциональных структурах данных за пределами простых List и Stream.)

Последняя опция - отказ от неизменности и превращение ее в изменяемую библиотеку (например, популярные библиотеки обработки изображений), которую мы действительно не хотим делать. Пожалуйста, предложите нам способ сохранить функциональность и функциональность библиотеки, если вы знаете, что я имею в виду.

Спасибо,
Сиддхарт Райна.

ДОПОЛНЕНИЕ:
Для изображения размером 1024 x 768, JVM заканчивается из кучи даже для одной операции отображения. Пример кода из нашего теста:

val image = Image from "E:/metallica.jpg"
val redded = image.map(_ & 0xff0000)
redded.display(title = "Redded")

И вывод:

"C:\Program Files (x86)\Java\jdk1.6.0_02\bin\java" -Didea.launcher.port=7533 "-Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 10.0.2\bin" -Dfile.encoding=windows-1252 -classpath "C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\rt.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.6.0_02\jre\lib\ext\sunpkcs11.jar;C:\new Ph\Phoebe\out\production\Phoebe;E:\Inventory\Marvin.jar;C:\scala-2.8.1.final\lib\scala-library.jar;C:\scala-2.8.1.final\lib\scala-swing.jar;C:\scala-2.8.1.final\lib\scala-dbc.jar;C:\new Ph;C:\scala-2.8.1.final\lib\scala-compiler.jar;E:\Inventory\commons-math-2.2.jar;E:\Inventory\commons-math-2.2-sources.jar;E:\Inventory\commons-math-2.2-javadoc.jar;E:\Inventory\jmathplot.jar;E:\Inventory\jmathio.jar;E:\Inventory\jmatharray.jar;E:\Inventory\Javax Media.zip;E:\Inventory\jai-core-1.1.3-alpha.jar;C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 10.0.2\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain phoebe.test.ImageTest
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at scala.collection.Iterator$class.toStream(Iterator.scala:1011)
    at scala.collection.IndexedSeqLike$Elements.toStream(IndexedSeqLike.scala:52)
    at scala.collection.Iterator$$anonfun$toStream$1.apply(Iterator.scala:1011)
    at scala.collection.Iterator$$anonfun$toStream$1.apply(Iterator.scala:1011)
    at scala.collection.immutable.Stream$Cons.tail(Stream.scala:565)
    at scala.collection.immutable.Stream$Cons.tail(Stream.scala:557)
    at scala.collection.immutable.Stream$$anonfun$map$1.apply(Stream.scala:168)
    at scala.collection.immutable.Stream$$anonfun$map$1.apply(Stream.scala:168)
    at scala.collection.immutable.Stream$Cons.tail(Stream.scala:565)
    at scala.collection.immutable.Stream$Cons.tail(Stream.scala:557)
    at scala.collection.immutable.Stream$$anonfun$flatten1$1$1.apply(Stream.scala:453)
    at scala.collection.immutable.Stream$$anonfun$flatten1$1$1.apply(Stream.scala:453)
    at scala.collection.immutable.Stream$Cons.tail(Stream.scala:565)
    at scala.collection.immutable.Stream$Cons.tail(Stream.scala:557)
    at scala.collection.immutable.Stream.length(Stream.scala:113)
    at scala.collection.SeqLike$class.size(SeqLike.scala:221)
    at scala.collection.immutable.Stream.size(Stream.scala:48)
    at scala.collection.TraversableOnce$class.toArray(TraversableOnce.scala:388)
    at scala.collection.immutable.Stream.toArray(Stream.scala:48)
    at phoebe.picasso.Image.force(Image.scala:85)
    at phoebe.picasso.SimpleImageViewer.<init>(SimpleImageViewer.scala:10)
    at phoebe.picasso.Image.display(Image.scala:91)
    at phoebe.test.ImageTest$.main(ImageTest.scala:14)
    at phoebe.test.ImageTest.main(ImageTest.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:115)

Process finished with exit code 1
4b9b3361

Ответ 1

Если я правильно понял, вы сохраняете каждый отдельный пиксель в одном элементе Stream, и это может быть неэффективным. Вы можете создать свой собственный класс LazyRaster, который содержит ленивые ссылки на блоки изображения определенного размера (например, 20x20). В первый раз, когда записывается какой-либо блок, его соответствующий массив инициализируется, а оттуда при изменении пикселя означает запись в этот массив.

Это больше работает, но может привести к повышению производительности. Кроме того, если вы хотите поддерживать стекирование операций с изображениями (например, сделать карту-берет-карту), а затем оценивать изображение в режиме "один шаг", реализация может получить сложную реализацию потока, это лучшее доказательство этого.

Еще одна вещь, которую можно сделать, - убедиться, что старый Stream находится правильно собранный мусор. Я подозреваю, что объект image в вашем примере является оберткой для ваших потоков. Если вы хотите объединить несколько операций с изображениями (например, сопоставление) и сможете использовать ссылки, которые вам больше не нужны, вы должны убедиться, что не используете ссылки на поток - обратите внимание, что это не обеспечивается, если:

  • у вас есть ссылка на ваше изображение в стеке (image в примере)
  • Ваша обертка image содержит такую ​​ссылку.

Не зная больше о конкретных случаях использования, трудно сказать больше.

Лично я бы вообще избегал Stream и просто использовал некоторую неизменяемую структуру данных на основе массива, которая является как экономичной по площади, так и избегает бокса. Единственное место, где я могу видеть Stream, - это итеративные преобразования изображений, такие как свертка или применение стека фильтров. Вместо этого у вас не было бы Stream пикселей, а Stream изображений. Это может быть хорошим способом выражения последовательности преобразований - в этом случае применяются комментарии к gc в приведенной выше ссылке.

Ответ 2

Если вы обрабатываете большие потоки, вам не нужно удерживать ссылку на головку потока. Это предотвратит сбор мусора.

Возможно, что вызов определенных методов на Stream будет внутренне удерживаться на голове. См. Обсуждение здесь: Функциональная обработка потоков Scala без ошибок OutOfMemory

Ответ 3

Stream вряд ли будет оптимальной структурой здесь. Учитывая природу JPEG, нет смысла "потокобежать" его в память по очереди.

Поток также имеет время линейного доступа для элементов считывания. Опять же, возможно, не то, что вам нужно, если вы не передаете данные.

Я бы рекомендовал использовать IndexedSeq[IndexedSeq[Int]] в этом сценарии. Или (если важна производительность) Array[Array[Int]], который позволит вам избежать некоторых расходов на бокс/распаковку.

Мартин написал хороший обзор API 2.8 коллекций, который поможет вам понять присущие компромиссы в доступных типах коллекций.

Даже при использовании массивов все еще есть все основания использовать их в качестве неизменяемых структур и поддерживать функциональный стиль программирования. Просто потому, что структура изменена, не означает, что вам нужно ее мутировать!

Ответ 4

Я также рекомендую рассматривать непрерывные, а не просто дискретные модели для изображений. Непрерывный, как правило, более модульный/составной, чем дискретный - время или пространство.

Ответ 5

В качестве первого шага вы должны взять дамп памяти и проанализировать его. Очень возможно, что вы сразу увидите проблему.

Существует специальная опция командной строки, чтобы заставить JVM делать дамп на OOME: -XX: + HeapDumpOnOutOfMemoryError. И хорошие инструменты, такие как jhat и VisualVM, которые могут помочь вам в анализе.

Ответ 6

Поток больше относится к ленивой оценке, чем к неизменности. И вы заставляя безумное количество пространства и времени накладных расходов для каждого пикселя делать это. Кроме того, Streams имеет смысл только тогда, когда вы можете отложить определение (вычисление или поиск) отдельных значений пикселей. И, конечно, случайный доступ невозможно. Я должен был бы считать Поток полностью неприемлемой структуры данных для обработки изображений.

Я настоятельно рекомендую вам управлять собственной растровой памятью (бонус указывает на то, что вы не устанавливаете единую организацию растрового изображения в свою код) и распределять память для целых каналов или плоскостей или полос (в зависимости от организации растра в игре).

ОБНОВЛЕНИЕ. В силу вышеизложенного я имею в виду не использовать вложенный массив или индексированныйSeq, а выделять блок и вычислять, какой элемент использует значения строк и столбцов.

Затем возьмите подход "неизменяемый после инициализации". Как только данный пиксель или образец были установлены в растре, вы никогда не позволяете это быть измененным. Это может потребовать однобитовой растровой плоскости для отслеживания установленных пикселей. Кроме того, если вы знаете, как вы будете заполнять растра (последовательность, в которой будут назначены пиксели), вы можете получить с гораздо более простым и более дешевым представлением о том, сколько растр установлен и сколько еще предстоит заполнить.

Затем, когда вы выполняете обработку на растровых изображениях, делайте это в конвейере где изображение не изменяется на месте, а скорее новое изображение всегда генерируемые при использовании различных преобразований.

Вы можете подумать, что для некоторых преобразований изображений (свертки, например), вы должны принять этот подход, иначе вы не получите правильное Результаты.

Ответ 8

Чтобы увеличить размер кучи с помощью intellij, вам необходимо добавить следующее в раздел "Параметры VM" конфигурации "Запуск/отладка":

-Xms256m -Xmx256m

Это увеличит максимальный размер кучи до 256 МБ, а также гарантирует, что эта сумма будет запрошена VM при запуске, что обычно представляет собой увеличение производительности.

Кроме того, вы используете относительно старый JDK. Если это возможно, я рекомендую вам обновить последнюю доступную версию, так как новые сборки позволяют использовать escape-анализ, который в некоторых случаях может оказать существенное влияние на производительность.

Теперь, с точки зрения алгоритмов, я предлагаю вам следовать приведенному выше совету и разделить изображение на блоки, скажем, 9x9 (любой размер будет делать). Затем я перейду и посмотрю Huet Zipper и подумает о том, как это можно применить к изображению, представленному в виде древовидной структуры, и о том, как что может позволить вам смоделировать изображение как постоянную структуру данных.

Ответ 9

Увеличение размера кучи в идее может быть выполнено в файле vmoptions, который можно найти в каталоге bin в каталоге установки вашей идеи (добавьте -Xmx512m, чтобы установить размер кучи на 512 мегабайт, например). Кроме того, трудно сказать, что вызывает нехватку памяти, не зная, какие операции вы выполняете, но, возможно, этот вопрос содержит некоторые полезные советы.

Ответ 10

Одним из решений было бы разместить изображение в массиве и сделать такие фильтры, как "map", вернуть оболочку для этого массива. В принципе, у вас есть черта с именем Image. Эта черта требует абстрактных операций по извлечению пикселей. Когда, например, вызывается функция "map", вы возвращаете реализацию, которая делегирует вызовы старому изображению и выполняет на нем функцию. Единственная проблема заключается в том, что преобразование может выполняться несколько раз, но поскольку это функциональная библиотека, это не очень важно.