Понимание использования памяти в Python

Я пытаюсь понять, как python использует память, чтобы оценить, сколько процессов я могу запускать одновременно. Сейчас я обрабатываю большие файлы на сервере с большим объемом оперативной памяти (~ 90-150 ГБ свободной оперативной памяти).

Для теста я бы сделал что-то на python, а затем посмотрел бы на htop, чтобы узнать, каково было использование.

Шаг 1. Я открываю файл размером 2,55 ГБ и сохраняю его в виде строки

with open(file,'r') as f:
    data=f.read()

Использование 2686M

шаг 2: я разделяю файл на новые строки

data = data.split('\n')

использование 7476M

шаг 3: я сохраняю только каждую четвертую строку (две из трех удаляемых строк имеют одинаковую длину с строкой, которую я сохраняю)

data=[data[x] for x in range(0,len(data)) if x%4==1]

использование 8543M

Шаг 4: я разделил это на 20 равных частей для работы в многопроцессорном пуле.

l=[] 
for b in range(0,len(data),len(data)/40):
    l.append(data[b:b+(len(data)/40)])

использование 8621M

шаг 5: удаляю данные, использование 8496M.

Есть несколько вещей, которые не имеют для меня смысла.

На втором этапе, почему использование памяти так сильно возрастает, когда я превращаю строку в массив. Я предполагаю, что контейнеры массива намного больше, чем контейнер строки?

на третьем шаге почему данные не сжимаются значительно. По сути, я избавился от 3/4 своих массивов и как минимум от 2/3 данных в массиве. Я ожидал, что он соответственно уменьшится. Вызов сборщика мусора не имел никакого значения.

как ни странно, когда я назначил меньший массив другой переменной, он использует меньше памяти. использование 6605M

когда удаляю старый объект data: использование 6059M

Мне это кажется странным. Любая помощь по уменьшению отпечатка моей памяти будет оценена.

РЕДАКТИРОВАТЬ

Ладно, у меня голова болит. Ясно, что python делает здесь какие-то странные вещи за кулисами ... и только python. Я сделал следующий сценарий, чтобы продемонстрировать это, используя свой оригинальный метод и метод, предложенный в ответе ниже. Все числа указаны в ГБ.

ИСПЫТАТЕЛЬНЫЙ КОД

import os,sys
import psutil
process = psutil.Process(os.getpid())
import time

py_usage=process.memory_info().vms / 1000000000.0
in_file = "14982X16.fastq"

def totalsize(o):
    size = 0
    for x in o:
        size += sys.getsizeof(x)
    size += sys.getsizeof(o)
    return "Object size:"+str(size/1000000000.0)

def getlines4(f):
    for i, line in enumerate(f):
        if i % 4 == 1:
            yield line.rstrip()

def method1():
    start=time.time()
    with open(in_file,'rb') as f:
        data = f.read().split("\n")
    data=[data[x] for x in xrange(0,len(data)) if x%4==1]
    return data

def method2():
    start=time.time()
    with open(in_file,'rb') as f:
        data2=list(getlines4(f))
    return data2


print "method1 == method2",method1()==method2()
print "Nothing in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
data=method1()
print "data from method1 is in memory"
print "method1", totalsize(data)
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
del data
print "Nothing in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
data2=method2()
print "data from method2 is in memory"
print "method2", totalsize(data2)
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
del data2
print "Nothing is in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage


print "\nPrepare to have your mind blown even more!"
data=method1()
print "Data from method1 is in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
data2=method2()
print "Data from method1 and method 2 are in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
data==data2
print "Compared the two lists"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
del data
print "Data from method2 is in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage
del data2
print "Nothing is in memory"
print "Usage:", (process.memory_info().vms / 1000000000.0) - py_usage

ВЫХОД

method1 == method2 True
Nothing in memory
Usage: 0.001798144
data from method1 is in memory
method1 Object size:1.52604683
Usage: 4.552925184
Nothing in memory
Usage: 0.001798144
data from method2 is in memory
method2 Object size:1.534815518
Usage: 1.56932096
Nothing is in memory
Usage: 0.001798144

Prepare to have your mind blown even more!
Data from method1 is in memory
Usage: 4.552925184
Data from method1 and method 2 are in memory
Usage: 4.692287488
Compared the two lists
Usage: 4.692287488
Data from method2 is in memory
Usage: 4.56169472
Nothing is in memory
Usage: 0.001798144

для тех из вас, кто использует python3, он довольно похож, за исключением того, что после операции сравнения не так плохо ...

ВЫХОД ИЗ PYTHON3

method1 == method2 True
Nothing in memory
Usage: 0.004395008000000006
data from method1 is in memory
method1 Object size:1.718523294
Usage: 5.322555392
Nothing in memory
Usage: 0.004395008000000006
data from method2 is in memory
method2 Object size:1.727291982
Usage: 1.872596992
Nothing is in memory
Usage: 0.004395008000000006

Prepare to have your mind blown even more!
Data from method1 is in memory
Usage: 5.322555392
Data from method1 and method 2 are in memory
Usage: 5.461917696
Compared the two lists
Usage: 5.461917696
Data from method2 is in memory
Usage: 2.747633664
Nothing is in memory
Usage: 0.004395008000000006

мораль этой истории ... память для питона, похоже, немного похожа на Камелот для Монти Пайтона ... это очень глупое место.

Действительно ли использование памяти сократилось на 3 порядка в битах «Использование составляет 8,543 Мбайт»?

user2357112 supports Monica 02.05.2018 00:21

Ясно, что нет, как вы можете видеть по моему редактированию;)

jeffpkamp 02.05.2018 00:23

"почему использование памяти так сильно возрастает, когда я превращаю строку в массив" в вашем коде нет массивов. Вы создали объект list. Раньше у вас была одна строка, представляющая собой один объект. Все в Python - это объект. Для каждого объекта у вас будут стандартные накладные расходы на объект в размере около 40-50 байт (в зависимости от системы, версии Python и т. д.). Итак, теперь у вас есть огромный список с множеством мелких объектов. Каждый указатель на объект будет составлять дополнительные 4-8 байтов.

juanpa.arrivillaga 02.05.2018 00:30

Кстати, какая у вас версия Python?

juanpa.arrivillaga 02.05.2018 00:32

Я считаю, что это 2.7.14 или 2.7.11, сейчас не могу проверить.

jeffpkamp 02.05.2018 00:37

Хорошо, тогда еще одна большая проблема в том, что вы делаете [data[x] for x in range(0,len(data)) if x%4==1]. Вместо этого используйте xrange. range материализует весь список, который, вероятно, довольно велик с учетом ваших данных.

juanpa.arrivillaga 02.05.2018 00:39

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

jeffpkamp 02.05.2018 01:08

Боковое примечание: for x in range(0,len(data)) if x%4==1 => for x in range(1,len(data),4)

Jacques Gaudin 02.05.2018 01:21

@ juanpa.arrivillaga Это верно только для CPython 2. В Python 3 и в обеих версиях PyPy range выделяет только небольшой объект постоянного пространства.

Jesin 02.05.2018 01:35

@Jesin да, это был мой ответ на OP о том, что они используют Python 2.

juanpa.arrivillaga 02.05.2018 01:51

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

juanpa.arrivillaga 02.05.2018 01:52
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
8
11
4 777
0

Другие вопросы по теме