2013-02-16 4 views
3

Я знаю на некоторое время, что Python любит повторно использовать строки в памяти, а не имеющих дубликатов:Строки из `raw_input()` в памяти

>>> a = "test" 
>>> id(a) 
36910184L 
>>> b = "test" 
>>> id(b) 
36910184L 

Однако, недавно я обнаружил, что возвращаемая строка из raw_input() не следует, что типичный шаблон оптимизации:

>>> a = "test" 
>>> id(a) 
36910184L 
>>> c = raw_input() 
test 
>>> id(c) 
45582816L 

Мне любопытно, почему это так? Есть ли техническая причина?

ответ

3

Мне кажется, что питон интерна строковых литералы, но строки, которые создаются с помощью какого-то другого процесса не получают интернированы:

>>> s = 'ab' 
>>> id(s) 
952080 
>>> g = 'a' if True else 'c' 
>>> g += 'b' 
>>> g 
'ab' 
>>> id(g) 
951336 

Конечно, raw_input создает новые строки без использования строковых литералов, так вполне возможно предположить, что у него не будет такого же id. Есть (по крайней мере) две причины, по которым C-python ставит строки - память (вы можете сохранить кучу, если не сохраняете целую кучу копий того же самого) и разрешение для хэш-коллизий. Если 2 строки хеш имеют одинаковое значение (в поиске словаря для примера), тогда python должен проверить, чтобы обе строки были эквивалентными. Это может выполнить сравнение строк, если они не интернированы, но если они интернированы, нужно лишь сравнить указатель, который немного эффективнее.

+0

@PauloScardine - Вероятно. Я не эксперт в источнике C - я время от времени занимаюсь spelunking :) – mgilson

+0

Кажется, что cpython всегда ставит строки размером в 0-1 байт, независимо от того, как они создаются: 's = 'a'; x = 'ab'; g = x [0]; print id (s), id (g) ' –

+0

@PauloScardine - Это неудивительно. Их всего 256, поэтому это не так много накладных расходов. Подобно тому, как это делается с помощью небольших ints. (От -5 до 255, я считаю). – mgilson

2

Компилятор не может содержать intern строк, за исключением случаев, когда они присутствуют в реальном исходном коде (то есть строковых литералах). В дополнение к этому, raw_input также удаляет новые строки.

+0

Кроме того, Python не ставит все строки. – elssar

+0

@elssar действительно - и я, кажется, вспоминаю много споров о ручном использовании 'intern' (но это было * лет * назад) –

+1

@JonClements - Видимо, вы можете вручную ставить строки, чтобы получить небольшое ускорение скорости в случае хеш-коллизии в поиске словаря. Тогда python может сравнивать указатель, а не строку, чтобы увидеть, являются ли строки одинаковыми. http://docs.python.org/2/library/functions.html#intern – mgilson

2

[обновление], чтобы ответить на вопрос, необходимо знать, почему, как и когда Python повторно использовать строки.

Давайте начнем с, как: Python использует «интернированы» строки - от wikipedia:

В информатике строка интернирование представляет собой способ хранения только одной копии каждого отдельного значения строки, которые должны быть неизменны , Внутренние строки делают некоторые задачи обработки строк более экономными по времени или пространству за счет того, что требуется больше времени при создании или интернировании строки. Разные значения хранятся в пуле пула.

Почему? Похоже, что сохранение памяти не главная цель здесь, только хороший побочный эффект.

Строка интернирование ускоряет сравнение строк, которые иногда узким местом производительности в приложениях (таких как компиляторы и динамических сред выполнения языка программирования), которые в значительной степени полагаются на хэш-таблицы с строковыми ключами. Без интернирования проверка того, что две разные строки равны, включает в себя изучение каждого символа обеих строк. Это медленно по нескольким причинам: по своей сути это O (n) в длине строк; обычно требуется чтение из нескольких областей памяти, которые требуют времени; и чтение считывает кеш процессора, что означает, что для других нужд меньше кеша. С интернированными строками после первоначальной статической операции достаточно простого теста на идентификацию объекта; это обычно реализуется как тест равенства указателя, как правило, только одна машинная команда без ссылки на память.

String interning также уменьшает использование памяти, если существует множество экземпляров одного и того же строкового значения; например, он считывается из сети или из хранилища. Такие строки могут включать магические числа или информацию о сетевом протоколе.Например, синтаксические анализаторы XML могут ставить имена тегов и атрибутов для сохранения памяти.

Теперь «когда»: CPython «интерны» строка в следующих ситуациях:

  • при использовании неосновной встроенной функции intern() (который переехал в sys.intern в Python 3) ,
  • небольшие строки (0 или 1 байты) - this very informative article by Laurent Luce объясняют об осуществлении
  • имен, используемых в программах Python автоматически интернированы
  • словарей, используемых для хранения модуля, атрибуты класса или экземпляра были интернированы ключи

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

я не мог поставить его лучше, чем Alex Martinelli сделал в this answer (не удивительно, что парень не имеет 245k репутации):

Каждая реализация языка Python вольна делать свои собственные компромиссы при распределении неизменных объектов (например, строки) - либо создание нового, либо поиск существующего равного, так и использование еще одной ссылки на него - отлично с точки зрения языка. На практике, конечно, реализация в реальном мире требует разумного компрометации: еще одна ссылка на подходящий существующий объект при поиске такого объекта дешева и проста, просто создайте новый объект, если задача поиска подходящего существующего (который может или может не существовать) выглядит так, как будто это может занять много времени.

Так, например, множественные вхождения одного и того же строкового литерала в пределах одной функции будут (во всех реализациях, которые я знаю) использовать стратегию «новая ссылка на тот же объект», потому что при создании константы-пула этой функции это довольно быстро и легко избежать дубликатов; но сделать это через отдельные функции потенциально может быть очень трудоемкой задачей, поэтому реалии реального мира либо не делают этого вообще, либо делают это только в некоторых эвристически определенных подмножествах дел, где можно надеяться на разумный компромисс между время компиляции (замедление путем поиска идентичных существующих констант) и потребление памяти (увеличивается, если сохраняются новые копии констант).

Я не знаю ни одной реализации Python (или, на самом деле, других языков с постоянными строками, таких как Java), которые берут на себя задачу идентифицировать возможные дубликаты (для повторного использования одного объекта через несколько ссылок) при чтении данных из файла - это просто не кажется многообещающим компромиссом (и здесь вы будете платить время выполнения, а не компилировать время, поэтому компромисс еще менее привлекателен). Конечно, если вы знаете (благодаря соображениям уровня приложения), что такие неизменные объекты большие и довольно подвержены многим дублированиям, вы можете легко реализовать собственную стратегию «константы-пул» (стажер может помочь вам сделать это для строк, но его нетрудно сворачивать, например, кортежи с неизменяемыми предметами, огромные длинные целые числа и т. д.).


[первоначальный ответ]

Это больше комментарий, чем ответ, но комментарий система не очень хорошо подходит для проводки кода:

def main(): 
    while True: 
     s = raw_input('feed me:') 
     print '"{}".id = {}'.format(s, id(s)) 

if __name__ == '__main__': 
    main() 

Бега это дает мне:

"test".id = 41898688 
"test".id = 41900848 
"test".id = 41898928 
"test".id = 41898688 
"test".id = 41900848 
"test".id = 41898928 
"test".id = 41898688 

Из моего опыта, по крайней мере, 2,7, есть некоторая оптимизация, продолжающаяся даже для raw_input().

Если реализация использует хеш-таблицы, я думаю, что существует более одного. Собираемся погрузиться в источник прямо сейчас.

[первое обновление]

Похоже, мой эксперимент был испорчен:

def main(): 
    storage = [] 
    while True: 
     s = raw_input('feed me:') 
     print '"{}".id = {}'.format(s, id(s)) 
     storage.append(s) 

if __name__ == '__main__': 
    main() 

Результат:

"test".id = 43274944 
"test".id = 43277104 
"test".id = 43487408 
"test".id = 43487504 
"test".id = 43487552 
"test".id = 43487600 
"test".id = 43487648 
"test".id = 43487744 
"test".id = 43487864 
"test".id = 43487936 
"test".id = 43487984 
"test".id = 43488032 

В своем answer to another question пользователь tzot предупреждает об объекте жизни:

Замечание: очень важно знать время жизни объектов в Python. Обратите внимание на следующую сессию:

Python 2.6.4 (r264:75706, Dec 26 2009, 01:03:10) 
[GCC 4.3.4] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
>>> a="a" 
>>> b="b" 
>>> print id(a+b), id(b+a) 
134898720 134898720 
>>> print (a+b) is (b+a) 
False 

Ваше мышление, что печатая идентификаторы двух отдельных выражений и отметив, что «они равны Ergo два выражения должны быть равны/эквивалент/то же самое» является неисправен. Одна строка вывода не обязательно означает, что все ее содержимое было создано и/или сосуществует в один и тот же момент времени.

Если вы хотите узнать, являются ли два объекта одним и тем же объектом, спросите Python напрямую (используя оператор is).

+0

Это интересно. – mgilson

+1

, вы получите тот же результат, даже если используете разные входы. здесь происходит сборка мусора. – dnozay

+0

На самом деле я лгу, я получаю тот же результат даже с 'gc.disable()'. – dnozay

Смежные вопросы