У меня есть большой файл, который я читаю из и преобразовываю каждые несколько строк в экземпляр Объекта.
Так как я - цикличное выполнение через файл, я прячу экземпляр к списку с помощью list.append (экземпляр) и затем продолжаю цикличное выполнение.
Это - файл, который это вокруг ~100MB, таким образом, это не является слишком большим, но поскольку список растет, цикличное выполнение прогрессивно замедляется. (Я печатаю время для каждой полировки в цикле).
Это не внутренне циклу ~, когда я печатаю каждый новый экземпляр, поскольку я циклично выполняюсь через файл, прогресс программы в постоянной скорости ~, это только, когда я добавляю их к списку, это становится медленным.
Мой друг предложил отключить сборку "мусора" перед циклом с условием продолжения и включить его позже и выполнить вызов сборки "мусора".
Кто-либо еще наблюдал подобную проблему с получением list.append медленнее? Там какой-либо другой путь состоит в том, чтобы обойти это?
Я попробую следующие две вещи, предложенные ниже.
(1) "предварительное выделение" памяти ~, что лучший способ состоит в том, чтобы сделать это? (2) Попытайтесь использовать двухстороннюю очередь
Несколько сообщений (см. комментарий Alex Martelli) предложенный фрагментацию памяти (у него есть большой объем доступной памяти как, я делаю), ~, но не очевидные меры к производительности для этого.
Для тиражирования явления выполните тестовый код, предоставленный ниже в ответах, и предположите, что списки имеют полезные данные.
gc.disable () и gc.enable () помогает с синхронизацией. Я также сделаю тщательный анализ того, где все время потрачен.
Низкая производительность, которую вы наблюдаете, вызвана ошибкой в сборщике мусора Python в используемой вами версии. Выполните обновление до Python 2.7 или 3.1 или выше, чтобы восстановить амортизированное поведение 0 (1), ожидаемое при добавлении списка в Python.
Если вы не можете выполнить обновление, отключите сборку мусора при построении списка и включите его после завершения.
(Вы также можете настроить триггеры сборщика мусора или выборочно вызывать сборщик по мере продвижения, но я не рассматриваю эти варианты в этом ответе, потому что они более сложные, и я подозреваю, что ваш вариант использования поддается описанному выше решению.)
Справочная информация:
См .: https: //bugs.python.org / issue4074 , а также https://docs.python.org/release/2.5.2/lib/module-gc.html
Репортер отмечает, что добавление сложных объектов (объектов, которые не числа или строки) в список линейно замедляется по мере увеличения длины списка.
Причина такого поведения в том, что сборщик мусора проверяет и перепроверяет каждый объект в списке, чтобы узнать, подходят ли они для сборки мусора. Такое поведение приводит к линейному увеличению времени добавления объектов в список. Ожидается, что исправление появится в py3k, поэтому оно не должно применяться к используемому вами интерпретатору.
Тест:
Я провел тест, чтобы продемонстрировать это. Для 1k итераций я добавляю 10k объектов в список и записываю время выполнения для каждой итерации. Общая разница во времени работы очевидна. Когда сборка мусора отключена во время внутреннего цикла теста, время выполнения в моей системе составляет 18,6 с. При включенной сборке мусора для всего теста время выполнения составляет 899,4 с.
Это тест:
import time
import gc
class A:
def __init__(self):
self.x = 1
self.y = 2
self.why = 'no reason'
def time_to_append(size, append_list, item_gen):
t0 = time.time()
for i in xrange(0, size):
append_list.append(item_gen())
return time.time() - t0
def test():
x = []
count = 10000
for i in xrange(0,1000):
print len(x), time_to_append(count, x, lambda: A())
def test_nogc():
x = []
count = 10000
for i in xrange(0,1000):
gc.disable()
print len(x), time_to_append(count, x, lambda: A())
gc.enable()
Полный источник: https://hypervolu.me/~erik/programming/python_lists/listtest.py.txt
Графический результат: красный цвет с включенным gc, синий с выключенным gc. По оси ординат отложены секунды в логарифмическом масштабе.
(источник: hypervolu.me )
Поскольку два графика различаются на несколько порядков по компоненту y, здесь они представлены независимо, ось Y масштабируется линейно.
(источник: hypervolu.me )
(источник: hypervolu.me )
Интересно, что при отключенной сборке мусора мы видим лишь небольшие всплески времени выполнения на 10 КБ дополнений, что говорит о том, что затраты на перераспределение списков Python относительно низки. В любом случае они на много порядков ниже затрат на сборку мусора.
Плотность вышеупомянутых графиков затрудняет понимание того, что при включенном сборщике мусора большинство интервалов действительно имеют хорошую производительность; только тогда, когда сборщик мусора циклически повторяет цикл, мы сталкиваемся с патологическим поведением. Вы можете наблюдать это на этой гистограмме времени добавления 10k. Большинство точек данных падают примерно на 0,02 с на 10 тыс. Добавлений.
(источник: hypervolu.me )
Необработанные данные, использованные для создания этих графиков, можно найти на http://hypervolu.me/~erik/programming/python_lists/
Обойти нечего: добавление в список амортизируется за O (1).
Список (в CPython) - это массив, по крайней мере, такой же длины, как список, но не более чем в два раза длиннее. Если массив не заполнен, добавить в список так же просто, как назначить один из членов массива (O (1)). Каждый раз, когда массив заполняется, его размер автоматически увеличивается вдвое. Это означает, что иногда требуется операция O (n), но она требуется только каждые n операций , и она требуется все реже по мере того, как список становится большим. О (п) / п ==> О (1). (В других реализациях имена и детали могут потенциально измениться, но в то же время свойства должны поддерживаться.)
Добавление к списку уже масштабируется.
Возможно ли, что когда файл становится большим, вы не можете хранить все в памяти и сталкиваетесь с проблемами при подкачке страниц ОС на диск? Возможно ли, что другая часть вашего алгоритма плохо масштабируется?
Многие из этих ответов - просто дикие догадки. Мне больше всего нравится Майк Грэм, потому что он прав насчет того, как реализованы списки. Но я написал код, чтобы воспроизвести ваше утверждение и изучить его подробнее. Вот некоторые выводы.
Вот с чего я начал.
import time
x = []
for i in range(100):
start = time.clock()
for j in range(100000):
x.append([])
end = time.clock()
print end - start
Я просто добавляю пустые списки в список x
. Я распечатываю продолжительность для каждых 100 000 добавлений 100 раз. Он действительно замедляется, как вы утверждали. (0,03 секунды для первой итерации и 0,84 секунды для последней ... большая разница.)
Очевидно, что если вы создаете экземпляр списка, но не добавляете его в x
, он запускается быстрее и не масштабируется со временем.
Но если вы измените x.append ([])
на x.append ('hello world')
, скорость вообще не увеличится. Один и тот же объект попадает в список 100 * 100 000 раз.
Что я думаю об этом:
Что касается внутреннего устройства Python, которое могло бы это объяснить, я не уверен. Но я почти уверен, что структура данных списка не виновата.
Вы можете попробовать http://docs.python.org/release/2.5.2/lib/deque-objects.html выделить ожидаемое количество необходимых элементов в вашем списке? ? Готов поспорить, что этот список представляет собой непрерывное хранилище, которое необходимо перераспределять и копировать каждые несколько итераций.
(аналогично некоторым популярным реализациям std :: vector в C ++)
РЕДАКТИРОВАТЬ: Поддерживается http://www.python.org/doc/faq/general/#how-are-lists-implemented