Я пытаюсь понять, как 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
мораль этой истории ... память для питона, похоже, немного похожа на Камелот для Монти Пайтона ... это очень глупое место.
Ясно, что нет, как вы можете видеть по моему редактированию;)
"почему использование памяти так сильно возрастает, когда я превращаю строку в массив" в вашем коде нет массивов. Вы создали объект list
. Раньше у вас была одна строка, представляющая собой один объект. Все в Python - это объект. Для каждого объекта у вас будут стандартные накладные расходы на объект в размере около 40-50 байт (в зависимости от системы, версии Python и т. д.). Итак, теперь у вас есть огромный список с множеством мелких объектов. Каждый указатель на объект будет составлять дополнительные 4-8 байтов.
Кстати, какая у вас версия Python?
Я считаю, что это 2.7.14 или 2.7.11, сейчас не могу проверить.
Хорошо, тогда еще одна большая проблема в том, что вы делаете [data[x] for x in range(0,len(data)) if x%4==1]
. Вместо этого используйте xrange
. range
материализует весь список, который, вероятно, довольно велик с учетом ваших данных.
Я предполагаю, что список диапазонов будет собран довольно быстро. Из всего, что я читал, gc.collect()
заставит сборщик мусора сразу же запуститься, и я не вижу никаких изменений после его запуска в конце каждого из этих шагов. Я немного запущу его с xrange и доложу.
Боковое примечание: for x in range(0,len(data)) if x%4==1
=> for x in range(1,len(data),4)
@ juanpa.arrivillaga Это верно только для CPython 2. В Python 3 и в обеих версиях PyPy range
выделяет только небольшой объект постоянного пространства.
@Jesin да, это был мой ответ на OP о том, что они используют Python 2.
@jeffpkamp Как я уже говорил, gc
- это только сборщик мусора с циклическими ссылками. Это не повлияет на что-нибудь, который вы здесь делаете (если нет циклов создания ссылок, которые вы нам не показываете). В любом случае, память может быть повторно востребована, но когда процесс Python решает вернуть ее в ОС, это будет зависеть от многих вещей.
Действительно ли использование памяти сократилось на 3 порядка в битах «Использование составляет 8,543 Мбайт»?