Уменьшение использования памяти с помощью объектов Python, которые используются с помощью колбы
Я ищу несколько советов о том, как уменьшить использование памяти для Python. Я использую этот фрагмент кода в качестве основной структуры для хранения моих данных:
http://stevehanov.ca/blog/index.php?id=114
Мне нужно, чтобы он служил для сопоставления слов близости с помощью фляги сервера. Мне нужно поставить гораздо больше 20 миллионов разных строк (и это будет увеличиваться). Теперь я получаю MemoryError при попытке положить около 14 миллионов в Trie.
Я просто добавляю словарь для хранения некоторого значения с быстрым доступом (он мне нужен, но его можно рассматривать как некий идентификатор внешнего вида, он не имеет прямого отношения к слову)
class TrieNode:
values = {}
def __init__(self):
self.word = None
self.children = {}
global NodeCount
NodeCount += 1
def insert( self, word, value):
node = self
for letter in word:
if letter not in node.children:
node.children[letter] = TrieNode()
node = node.children[letter]
TrieNode.values[word] = value
node.word = word
Я не знаком с оптимизацией Python, есть ли способ сделать объект "письмо" менее большим, чтобы сэкономить память?
Пожалуйста, обратите внимание, что мои трудности связаны с тем, что это письмо не только [az], но и должно обрабатывать весь "диапазон Юникода" (например, акцентированные символы, но не только). Кстати, это один символ, поэтому он должен быть достаточно легким по отпечатку памяти. Как я могу использовать кодовую точку вместо строкового объекта (это будет более эффективно использовать память)?
РЕДАКТИРОВАТЬ: добавить некоторую другую информацию после ответа от @juanpa-Arrivillaga
Итак, во-первых, я не вижу разницы, используя конструкцию слота, на моем компьютере, с или без __slot__
Я вижу то же самое использование памяти.
с __slot__
:
>>> class TrieNode:
NodeCount = 0
__slots__ = "word", "children"
def __init__(self):
self.word = None
self.children = {}
#global NodeCount # my goal is to encapsulated the NodeCount in the class itself
TrieNode.NodeCount += 1
>>> tn = TrieNode()
>>> sys.getsizeof(tn) + sys.getsizeof(tn.__dict__)
176
без __slot__
:
>>> class TrieNode:
NodeCount = 0
def __init__(self):
self.word = None
self.children = {}
#global NodeCount
TrieNode.NodeCount += 1
>>> tn = TrieNode()
>>> sys.getsizeof(tn) + sys.getsizeof(tn.__dict__)
176
поэтому я не понимаю, почему. Где я не прав?
вот еще кое-что, что я тоже попробовал, используя ключевое слово intern, потому что это значение является строкой, обрабатывающей "id" (и поэтому не относится к юникоду, а не к букве):
Кстати, моя цель состояла в том, чтобы иметь со значениями и NodeCount, эквивалентную концепцию для класса / статических переменных, чтобы каждая из них использовалась всеми экземплярами небольших созданных объектов, я думал, что это сохранит память и позволит избежать дублирования, но я могу неправильно из моего понимания "статоподобной" концепции в Python)
class TrieNode:
values = {} # shared amon all instances so only one structure?
NodeCount = 0
__slots__ = "word", "children"
def __init__(self):
self.word = None
self.children = {}
#global NodeCount
TrieNode.NodeCount += 1
def insert( self, word, value = None):
# value is a string id like "XYZ999999999"
node = self
for letter in word:
codepoint = ord(letter)
if codepoint not in node.children:
node.children[codepoint] = TrieNode()
node = node.children[codepoint]
node.word = word
if value is not None:
lost = TrieNode.values.setdefault(word, [])
TrieNode.values[word].append(intern(str(value)))
ДОБАВЛЕНО: Наконец, я должен был уточнить, что я использую семейство Python 2.7.x.
Мне было интересно, есть ли какие-либо фиксированные типы данных len из библиотеки, такие как numpy, может помочь мне сэкономить память, опять же, как новую, я не знаю, где искать. Кстати, "слово" - это не реальное "слово на естественном языке", а "последовательность символов произвольной длины", и они также могут быть очень длинными.
Исходя из вашего ответа, я согласен с тем, что избегать сохранения слова в каждом узле было бы эффективно, но вам нужно взглянуть на связанную статью / фрагмент кода. Основная цель не в том, чтобы восстановить это слово, а в том, чтобы сделать эффективное / очень быстрое приблизительное сопоставление строк, используя это слово, а затем получить "значение", связанное с каждым из самых близких совпадений, я не уверен, что понял цель пути вниз к дереву. (не доходя до полного дерева?), и при сопоставлении нам просто нужно подобрать оригинальное слово (но мое понимание может быть неверным в этой точке).
поэтому мне нужно, чтобы где-то был этот огромный дикт, и я хотел, чтобы в классе было удобно. Но может быть, это слишком дорого с точки зрения "веса" памяти?
Также я заметил, что я получаю уже меньше памяти, чем ваш образец (пока не знаю, почему), но вот пример значения "буквы", содержащегося в структуре.
>>> s = u"\u266f"
>>> ord(s)
9839
>>> sys.getsizeof(s)
28
>>> sys.getsizeof(ord(s))
12
>>> print s
♯
>>> repr(s)
"u'\\u266f'"
1 ответ
Низко висящие фрукты: используйте __slots__
в вашем классе узла, в противном случае, каждый TrieNode
объект носит вокруг dict
,
class TrieNode:
__slots__ = "word", "children"
def __init__(self):
self.word = None
self.children = {}
Теперь каждый объект TrieNode не будет содержать атрибут dict
, Сравните размеры:
>>> class TrieNode:
... def __init__(self):
... self.word = None
... self.children = {}
...
>>> tn = TrieNode()
>>> sys.getsizeof(tn) + sys.getsizeof(tn.__dict__)
168
Vs:
>>> class TrieNode:
... __slots__ = "word", "children"
... def __init__(self):
... self.is_word = False
... self.children = {}
...
>>> sys.getsizeof(tn)
56
>>> tn.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'TrieNode' object has no attribute '__dict__'
Еще одна оптимизация, использовать int
объекты. Маленький int
объекты кэшируются, вероятно, большинство ваших персонажей все равно будут находиться в этом диапазоне, но даже если это не так, int
хотя в Python все еще громоздкий, он меньше, чем даже одна строка символов:
>>> 'ñ'
'ñ'
>>> ord('ñ')
241
>>> sys.getsizeof('ñ')
74
>>> sys.getsizeof(ord('ñ'))
28
Так что вы можете сделать что-то вроде:
def insert( self, word, value):
node = self
for letter in word:
code_point = ord(letter)
if code_point not in node.children:
node.children[code_point] = TrieNode()
node = node.children[code_point]
node.is_word = True #Don't save the word, simply a reference to a singleton
Кроме того, вы держите вокруг переменной класса values
dict
это чрезвычайно растет, но эта информация избыточна. Ты говоришь:
Я просто добавляю словарь для хранения некоторого значения с быстрым доступом (он мне нужен)
Вы можете восстановить слова с пути. Это должно быть относительно быстро, я бы серьезно подумал против этого dict
, Посмотрите, сколько памяти требуется для хранения миллиона односимвольных строк:
>>> d = {str(i):i for i in range(1000000)}
>>> (sum(sizeof(k)+sizeof(v) for k,v in d.items()) + sizeof(d)) * 1e-9
0.12483203000000001
Вы могли бы сделать что-то вроде:
class TrieNode:
__slots__ = "value", "children"
def __init__(self):
self.value = None
self.children = {}
def insert( self, word, value):
node = self
for letter in word:
code_point = ord(letter)
if code_point not in node.children:
node.children[code_point] = TrieNode()
node = node.children[code_point]
node.value = value #this serves as a signal that it is a word
def get(word, default=None):
val = self._get_value(word)
if val is None:
return default
else:
return val
def _get_value(self, word):
node = self
for letter in word:
code_point = ord(letter)
try:
node = node.children[code_point]
except KeyError:
return None
return node.value