Występują problemy z zarządzaniem pamięcią związaną z bytes
w Python3.2. W niektórych przypadkach bufor ob_sval
wydaje się zawierać pamięć, której nie mogę uwzględnić.Jak zapewnić, że pamięć "zer" Pythona zostanie zebrana?
Dla konkretnej bezpiecznej aplikacji muszę mieć pewność, że pamięć jest "wyzerowana" i zwrócona do systemu operacyjnego tak szybko, jak to możliwe, po tym, jak nie jest już używana. Od ponownej kompilacji Python nie jest to opcja, Piszę moduł, który może być używany z LD_PRELOAD do:
- wyłączenia buforowania pamięci zastępując
PyObject_Malloc
zPyMem_Malloc
,PyObject_Realloc
zPyMem_Realloc
iPyObject_Free
zPyMem_Free
(na przykład: co byś otrzymał, gdybyś skompilował to bezWITH_PYMALLOC
). Nie obchodzi mnie, czy pamięć jest połączona, czy nie, ale wydaje się to najłatwiejsze. - Okłady
malloc
,realloc
ifree
tak aby śledzić, ile pamięci jest wymagane imemset
wszystkiego do0
gdy jest on zwolniony.
Przy pobieżnym spojrzeniem, takie podejście wydaje się działać świetnie:
>>> from ctypes import string_at
>>> from sys import getsizeof
>>> from binascii import hexlify
>>> a = b"Hello, World!"; addr = id(a); size = getsizeof(a)
>>> print(string_at(addr, size))
b'\x01\x00\x00\x00\xd4j\xb2x\r\x00\x00\x00<J\xf6\x0eHello, World!\x00'
>>> del a
>>> print(string_at(addr, size))
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00'
błędny \x13
na końcu jest dziwne, ale nie pochodzi z mojej pierwotnej wartości, więc na pierwszy Sądziłem, że to w porządku . I szybko znaleźć przykłady, gdzie rzeczy nie były tak dobre, choć:
>>> a = b'Superkaliphragilisticexpialidocious'; addr = id(a); size = getsizeof(a)
>>> print(string_at(addr, size))
b'\x01\x00\x00\x00\xd4j\xb2x#\x00\x00\x00\x9cb;\xc2Superkaliphragilisticexpialidocious\x00'
>>> del s
>>> print(string_at(addr, size))
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00))\n\x00\x00ous\x00'
Oto ostatnie trzy bajty, ous
, przeżył.
Więc moje pytanie:
Co się dzieje z resztek bajtów dla bytes
obiektów, a dlaczego nie zostaną usunięte podczas del
nazywa się na nich?
Zgaduję, że w moim podejściu brakuje czegoś podobnego do realloc
, ale nie widzę, co to byłoby w bytesobject.c
.
Podjęto próbę ilościowego określenia liczby "pozostawionych" bajtów, które pozostają po zbiorze śmieci i wydaje się, że jest do przewidzenia w pewnym stopniu.
from collections import defaultdict
from ctypes import string_at
import gc
import os
from sys import getsizeof
def get_random_bytes(length=16):
return os.urandom(length)
def test_different_bytes_lengths():
rc = defaultdict(list)
for ii in range(1, 101):
while True:
value = get_random_bytes(ii)
if b'\x00' not in value:
break
check = [b for b in value]
addr = id(value)
size = getsizeof(value)
del value
gc.collect()
garbage = string_at(addr, size)[16:-1]
for jj in range(ii, 0, -1):
if garbage.endswith(bytes(bytearray(check[-jj:]))):
# for bytes of length ii, tail of length jj found
rc[jj].append(ii)
break
return {k: len(v) for k, v in rc.items()}, dict(rc)
# The runs all look something like this (there is some variation):
# ({1: 2, 2: 2, 3: 81}, {1: [1, 13], 2: [2, 14], 3: [3, 4, 5, 6, 7, 8, 9, 10, 11, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 83, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]})
# That is:
# - One byte left over twice (always when the original bytes object was of lengths 1 or 13, the first is likely because of the internal 'characters' list kept by Python)
# - Two bytes left over twice (always when the original bytes object was of lengths 2 or 14)
# - Three bytes left over in most other cases (the exact ones varies between runs but never has '12' in it)
# For added fun, if I replace the get_random_bytes call with one that returns an encoded string or random alphanumerics then results change slightly: lengths of 13 and 14 are now fully cleared too. My original test string was 13 bytes of encoded alphanumerics, of course!
Edycja 1
miałem pierwotnie wyraziła zaniepokojenie faktem, że jeśli obiekt bytes
jest używana w funkcji nie był sprzątany w ogóle:
>>> def hello_forever():
... a = b"Hello, World!"; addr = id(a); size = getsizeof(a)
... print(string_at(addr, size))
... del a
... print(string_at(addr, size))
... gc.collect()
... print(string_at(addr, size))
... return addr, size
...
>>> addr, size = hello_forever()
b'\x02\x00\x00\x00\xd4J0x\r\x00\x00\x00<J\xf6\x0eHello, World!\x00'
b'\x01\x00\x00\x00\xd4J0x\r\x00\x00\x00<J\xf6\x0eHello, World!\x00'
b'\x01\x00\x00\x00\xd4J0x\r\x00\x00\x00<J\xf6\x0eHello, World!\x00'
>>> print(string_at(addr, size))
b'\x01\x00\x00\x00\xd4J0x\r\x00\x00\x00<J\xf6\x0eHello, World!\x00'
Okazuje się, że jest to sztuczny problem, który nie jest objęty moimi wymaganiami. Możesz zobaczyć komentarze do tego pytania dla szczegółów, ale problem wynika z tego, że krotka hello_forever.__code__.co_consts
będzie zawierać odniesienie do Hello, World!
nawet po usunięciu z locals
.
W prawdziwym kodzie "bezpieczne" wartości pochodzą z zewnętrznego źródła i nigdy nie zostaną zakodowane na stałe, a następnie usunięte w ten sposób.
Edycja 2
ja również wyrażona niejasności co do zachowania z strings
. Zwrócono uwagę, że prawdopodobnie cierpią one również na ten sam problem co bytes
w odniesieniu do ich kodowania w funkcjach (np .: artefakt mojego kodu testowego). Występują z nimi jeszcze dwa inne zagrożenia, których nie udało mi się wykazać jako problem, ale będę dalej badał:
- Intrygowanie napisami odbywa się przez Pythona w różnych punktach w celu przyspieszenia dostępu. Nie powinno to stanowić problemu, ponieważ internowane ciągi powinny zostać usunięte, gdy utracono ostatnie odniesienie. Jeśli okaże się, że jest to problemem, powinna istnieć możliwość zamiany
PyUnicode_InternInPlace
, tak aby nic nie działo. - Ciągi i inne "pierwotne" typy obiektów w Pythonie często przechowują "wolną listę", aby przyspieszyć otrzymywanie pamięci dla nowych obiektów. Jeśli okaże się, że jest to problem, metody
*_dealloc
można zastąpić metodami.
Wierzyłem również, że widzę problem z nieprawidłowym wyzerowaniem klas, ale teraz uważam, że był to błąd z mojej strony.
Dzięki
Much thanks do @Dunes i @Kevin dla wskazując na problemy, które ukrywane moje pierwotne pytanie. Te kwestie zostały powyżej w punktach "edytuj" powyżej.
Python prawdopodobnie interweniuje w łańcuchach. – Kevin
Python definiuje tutaj ciągi znaków, są one przechowywane na liście stałych funkcji - 'hello_forever .__ kod __. Co_consts'. – Dunes
Czy rozważałeś zmianę makr '_Py_Dealloc' lub' Py_DECREF', aby wyzerować pamięć po dezalokacji? W przeciwieństwie do zakłócania przydziału pamięci. – Dunes