2013-08-04 10 views
7

Łańcuchy w języku Python są niezmienne i obsługują interfejs bufora. Tak więc wydajne jest zwracanie nie nowych łańcuchów, ale części starego łańcucha, gdy używamy plasterków lub metody split(). Ale, o ile mi wiadomo, za każdym razem konstruuje się nowy obiekt napisów. Dlaczego tak jest? Jedynym powodem, dla którego widzę, jest to, że może trochę utrudnić zbieranie śmieci.Niezmienne łańcuchy i ich plasterki w języku Python

Prawda: w regularnych sutuacjach obciążenie pamięci jest liniowe i nie jest zauważalne: kopiowanie jest szybkie, tak jak sądzę, przydział. Ale w pytonie jest zbyt wiele rzeczy do powiedzenia, że ​​nie jest to warte wysiłku!

EDIT:

Wydaje się, że za pomocą tego sposobu może sprawić, że zarządzanie pamięcią naprawdę bardziej komplikować. Przypadek, w którym 1/5 arbitralnego ciągu znaków jest używany tylko i nie można zwolnić całego ciągu, jest prostym przykładem. Możemy poprawić alokację pamięci, aby mógł częściowo zwolnić łańcuchy, ale prawdopodobnie byłby to głównie błąd. Wszystkie standardowe funkcje mogą być jednak emulowane za pomocą bufora lub widoku pamięci, jeśli użycie pamięci jest niezwykle ważne. Tak, kod nie będzie jeszcze tak zwięzły, ale musimy coś oddać, żeby coś dostać.

+1

W jaki sposób zwrócisz części sznurka? Masz na myśli wskazówki? co dzieje się z ciągiem podrzędnym, jeśli oryginalny ciąg został usunięty? –

+0

Co stanie się z buforem, jeśli oryginalny obiekt zostanie usunięty? Myślę, że kolekcja śmieci jest wystarczająco inteligentna i nie usuwa oryginalnego obiektu. – gukoff

+0

Wierzę, że twoje pytanie jest duplikatem [Jeśli łańcuchy są niezmienne w .NET, to dlaczego Substring zabiera O (n) czas?] (Http://stackoverflow.com/questions/6742923/if-strings-are-immutable -in-net-then-why-does-substring-take-on-time). Te same argumenty są poprawne dla Pythona. – Bakuriu

Odpowiedz

2

podstawowa reprezentacja ciągów znaków to zakończona znakiem Null, mimo że śledzi długość, dlatego też użytkownik nie może mieć obiektu tekstowego, który odwołuje się do pod-ciągu znaków to nie jest przyrostek. Ogranicza to już przydatność twojej propozycji, ponieważ spowodowałoby to wiele komplikacji w różnym traktowaniu z wystarczającymi i niewystarczającymi (a rezygnacja z łańcuchów kończących się wartością null przynosi inne konsekwencje).

Umożliwienie odnoszenia się do pod-ciągów ciągu oznacza komplikowanie partii czyszczenia i obsługi ciągów. Dla każdego ciągu musisz śledzić, ile obiektów odnosi się do każdego znaku lub do każdego zakresu indeksów. Oznacza to znaczne komplikowanie obiektów ciągów i wszelkich operacji, które się nimi zajmują, co oznacza, prawdopodobnie duży, spowolnienie.

Dodaj fakt, że zaczynając od ciągów python3 mają 3 różne reprezentacje wewnętrzne, a rzeczy będą zbyt niechlujne, aby można je było utrzymać, , a twoja propozycja prawdopodobnie nie daje wystarczających korzyści, aby zostać zaakceptowanym.


Innym problemem z tego rodzaju „optymalizacji” jest, gdy chcesz cofnąć przydział „duże ciągi”:

a = "Some string" * 10 ** 7 
b = a[10000] 
del a 

Po tej operacji masz podciąg b który zapobiega a, ogromny łańcuch , które zostaną zwolnione. Z pewnością można zrobić kopie małych ciągów, ale co jeśli b = a[:10000] (lub inny duży numer)? 10000 znaków wygląda na duży ciąg, który powinien użyć optymalizacji, aby uniknąć kopiowania, ale zapobiega ujawnianiu megabajtów danych. Odśmiecacz musiałby sprawdzać, czy warto zwolnić duży obiekt z napisami i wykonać kopie, czy też nie, a wszystkie te operacje muszą być tak szybkie, jak to możliwe, w przeciwnym razie zmniejszają się czasy wykonania.

99% razy, gdy ciągi używane w programach są "małe" (maksymalnie 10k znaków), dlatego kopiowanie jest naprawdę szybkie, a optymalizacje, które proponujesz, zaczynają działać z naprawdę dużymi ciągami (np. Weź podciągi o rozmiarze 100k od ogromnych tekstów) i są znacznie wolniejsze, z naprawdę małymi ciągami, co jest częstym przypadkiem, czyli przypadkiem, który powinien zostać zoptymalizowany.


Jeśli uważasz, że ważne wtedy jesteś wolny, aby zaproponować PEP, pokaż implementację i powstałe zmiany w wykorzystaniu prędkości/pamięci swojego wniosku. Jeśli naprawdę jest to warte wysiłku, może zostać uwzględniony w przyszłej wersji Pythona.

+1

Nie sądzę, że propozycja PO jest dobrym pomysłem, ale z różnych powodów ("nie strzelaj do wiadomości"). Pierwszy akapit jest interesujący, a na pewno problem z powiązaniem tego z CPythonem, ale nie jest to podstawowy problem i nie byłby to pierwszy raz, kiedy reprezentacja napisów zmienia się bardzo. Drugi akapit zakłada głupie wdrożenie. Wystarczy, że pod-łańcuch odwoła się do odpowiedniego obiektu łańcuchowego i zapisze początek i długość/koniec (i ta reprezentacja jest doskonale nieświadoma jajeczek obiektu, więc trzeci akapit znika w obłoku logiki). – delnan

+0

Fraza o stylu zakończonym znakiem pustym jest całkiem trafna. Ale to ciekawe: czy ten styl jest w ogóle przydatny? Dla funkcji takich jak strcpy, może, ale można je zmienić za pomocą funkcji takich jak strncpy ... – gukoff

+0

@Harold Nie sądzę, że jest to "strcpy", ponieważ długość jest dostępna, jest prostsze i szybsze po prostu "memcpy" - podobnie jak w przypadku innych standardowych funkcji. Prawdopodobnie uniknie się kopiowania podczas przekształcania go w łańcuch C dla kodu strony trzeciej/klienta (zobacz 'PyBytes_AsString' i tym podobne). – delnan

3

Tak działają plasterki. Plastry zawsze wykonać kopię płytkie, dzięki czemu można zrobić rzeczy jak

>>> x = [1,2,3] 
>>> y = x[:] 

Teraz byłoby to możliwe, aby uczynić wyjątek dla strun, ale jest to naprawdę warto? Eric Lippert blogged about his decision not to do that for .NET; Sądzę, że jego argument jest ważny również w Pythonie.

Zobacz także this question.

+0

Tak, jest to bardzo przydatne, na przykład podczas iterowania obiektu zmiennego. Ale tutaj, na struny? Nie naruszałoby to spójności. – gukoff

+0

@Harold: To prawda, ale być może nie jest to warte wysiłku. Edytowałem swoją odpowiedź. –

+0

Wygląda na to, że Eric próbował dokonać poważnej optymalizacji, nawet w przypadku konkatenacji. Tak więc klasa StringBuilder naprawdę nivelate potrzeby w takich złożonych optymalizacji w języku C#. – gukoff

2

Jeśli martwisz się o pamięci (w przypadku bardzo dużych ciągów), użyj buffer():

>>> a = "12345" 
>>> b = buffer(a, 2, 2) 
>>> b 
<read-only buffer for 0xb734d120, size 2, offset 2 at 0xb734d4a0> 
>>> print b 
34 
>>> print b[:] 
34 

Wiedząc o tym pozwala alternatywy dla metod strunowych takich jak split().

Jeśli chcesz split() ciąg, ale zachować oryginalny obiekt string (jak może to potrzebne), można zrobić:

def split_buf(s, needle): 
    start = None 
    add = len(needle) 
    res = [] 
    while True: 
     index = s.find(needle, start) 
     if index < 0: 
      break 
     res.append(buffer(s, start, index-start)) 
     start = index + add 
    return res 

lub korzystając .index():

def split_buf(s, needle): 
    start = None 
    add = len(needle) 
    res = [] 
    try: 
     while True: 
      index = s.index(needle, start) 
      res.append(buffer(s, start, index-start)) 
      start = index + add 
    except ValueError: 
     pass 
    return res 
+1

Tak, wiem o buforach. Ale co jeśli chcę użyć metody split() w dowolnym ciągu? – gukoff

+0

@Harold Możesz "emulować", spełniając twoje potrzeby, zobacz moją edycję. OTOH, jeśli podzielisz ciąg i już go nie potrzebujesz, możesz upuścić oryginał, zwalniając pamięć i mając mniej więcej taki sam ślad pamięci, jak wcześniej. – glglgl

Powiązane problemy