2017-02-18 22 views
3

Я написал программу, которая анализирует и выполняет операции с данными из файла. Моя первая реализация использует Data.ByteString для чтения содержимого файла. Это содержимое затем преобразуется в вектор образцов, используя Data.Vector.Unboxed. Затем я выполняю обработку и анализ этого (unboxed) вектора значений образца.Использование памяти для lazy datatypes

Как и эксперимент, я хотел знать, что произойдет, если я воспользуюсь ленивостью Хаскелла. Я решил сделать этот простой тест, используя Data.ByteString.Lazy вместо Data.ByteString и Data.Vector вместо Data.Vector.Unboxed. Я ожидал улучшения в использовании памяти. Даже если моя программа в конечном итоге потребует знать ценность каждого образца, я все равно ожидаю, что использование памяти будет возрастать постепенно. Когда я профилировал свою программу, результаты меня удивили.

Моей первоначальная версия заканчивается примерно 20мс и его использование памяти выглядит следующим образом: enter image description here Это выглядит как ленивое поведение для меня. Образцы, кажется, загружаются в память, поскольку они необходимы моей программе.

Использование Data.Vector и Data.ByteString дал следующий результат: enter image description here Это выглядит как противоположность ленивым поведения. Все образцы, кажется, загружаются в память сразу, а затем удаляются один за другим.

Я подозревал, что это имело какое-то отношение к моему непониманию типов Boxed и Unboxed, поэтому я попытался использовать Data.ByteString.Lazy с `Data.Vector.Unboxed '. Это было результатом: enter image description here Я понятия не имею, как объяснить, что я вижу здесь.

Может ли кто-нибудь объяснить результаты, которые я получаю?

EDIT я чтение из файла с помощью hGet, это дает мне Data.ByteString.Lazy. Я преобразовать этот байтовой строки к Data.Vector поплавков с помощью следующей функции:

toVector :: ByteString -> Vector Float 
toVector bs = U.generate (BS.length bs `div` 3) $ \i -> 
    myToFloat [BS.index bs (3*i), BS.index bs (3*i+1), BS.index bs (3*i+2)] 
    where 
    myToFloat :: [Word8] -> Float 
    myToFloat words = ... 

Поплавки представлены в 3 байта.

Остальная часть обработки в основном состоит из применения более высоких функций (например, filter, map и т. Д.) К данным.

EDIT2 Мой анализатор содержит функцию, которая считывает все данные из файла и возвращает эти данные в векторе выборок (с использованием предыдущей toVector функции). Я написал две версии этой программы: одну с Data.ByteString и одну с Data.ByteString.Lazy. Я использовал эти две версии, чтобы выполнить простой тест:

main = do 
    [file] <- getArgs 
    samples <- getSamplesFromFile file 
    let slice = V.slice 0 100000 samples 
    let filtered = V.filter (>0) slice 
    print filtered 

Строгая версия дает мне следующее использование памяти: enter image description here Ленивая версия дает мне следующее использование памяти: enter image description here Этому результат кажется быть полной противоположностью тому, что я ожидал бы. Может ли кто-нибудь объяснить это? Что не так с Data.ByteString.Lazy?

+0

Вы связывающая источник здесь? – Michael

+0

@ Майкл. Извините, я хочу, но мне не разрешено делиться источником. Есть ли дополнительная информация, которую я мог бы предоставить вам, не разделяя источник? –

+1

@ThomasVanhelden Вы можете подготовить минимальную демонстрацию проблемы, которая включает в себя только шаблоны генерации и потребления 'ByteString' /' Vector', а не код вашего домена. – duplode

ответ

3

Данные, которые мы имеем до сих пор, не являются адекватными для воспроизведения проблемы. Здесь я запускаю четыре версии http://sprunge.us/PeIJ, меняя строгий на ленивый и коробчатый на unboxed. Я компилирую с ghc -O2 -rtsopts -prof Единственное различие стоит отметить, что каждый действительный элемент (указатель) в векторе или потоке в версии Data.Vector указывает на себя за пределы своего собственного поплавка в виде шара, который занимает кучу пространства. Все в основном одинаково во всем, за исключением программ Data.Vector, как и ожидалось, большой кучи синего в верхней части для этих тщательно упакованных поплавков.

enter image description here

Редактировать Вот что я получу, если я просто использовать ghc -prof -rtsopts

enter image description here

+0

Это полностью отличается от того, что происходит в моей системе. Я запустил ваш код с вашими флагами компиляции, и мои результаты все те же, что и в моем вопросе. Просто переключение с 'Data.ByteString' на' Data.ByteString.Lazy' в вашем коде, заставляет его заняться гораздо большим объемом памяти (точно так же, как в моем втором редактировании). –

+0

Вы используете -O2, который является стандартным для 'vector' программ? (Вся библиотека скомпилирована с -O2) – Michael

+0

Да, я использую -O2. Фактически, я использую те же параметры ghc, что и вы. –

4

Вы используете length на ленивом байте. Это потребует целую строку.Если бы это было единственное использование входной ленивой байтовой строки, сборка мусора могла бы заставить ее работать в постоянном пространстве. Тем не менее, вы получаете доступ к строке после этого для дальнейших вычислений, заставляя все данные сохраняться в памяти.

Решение этого было бы полностью исключить length и попытаться сложить ленивую байтовую установку (только один раз!), Чтобы потоковая передача могла выполнять свою работу.

Вы можете, например, сделать что-то вроде

myread :: ByteString -> [Float] 
myread bs = case splitAt 3 bs of 
    ([x1,x2,x3], end) -> myToFloat x1 x2 x3 : myread end 
    -- TODO handle shorter data as well 

toVector bs = U.fromList $ myread bs 

Возможно есть лучший способ мобилизации Vector вещи. U.unfoldr выглядит многообещающим.

+1

Лесткая байтовая строка будет в памяти, но может ли она занимать гораздо больше места, чем соответствующая строгая байтовая последовательность? Я предполагал, что может возникнуть дополнительная проблема, если вектор 'Data.Vector' просто указывает на zillion thunks формы' myToFloat [BS.index bs (3 * i), BS.index bs (3 * i + 1), BS.index bs (3 * i + 2)] '. Думаю, размер таких вещей будет складываться. – Michael

+0

@ Michael AFAICS, это не должно занимать больше памяти больше, чем строгий вариант. Thunks следует оценивать, когда вы вставляете их в вектор _unboxed_ (в отличие от вставки). Вам действительно нужна байтовая последовательность после того, как вы создали свой вектор? Если вы больше не используете его, должно быть возможно заставить его работать потоковым способом, потребляя только память O (1). (Вектор по-прежнему будет использовать O (n), конечно) – chi

+0

Идея (полностью полуобработанная) заключалась в том, что снижение использования памяти во втором изображении было связано с последовательным уменьшением громкости до плавания вещей (в штучной упаковке) вектора указывал. – Michael