Python: медленное чтение и запись для миллионов маленьких файлов - PullRequest
14 голосов
/ 13 июня 2010

Вывод: Кажется, что HDF5 - это путь для моих целей.В основном « HDF5 - это модель данных, библиотека и формат файлов для хранения и управления данными. » и предназначена для обработки невероятных объемов данных.Он имеет модуль Python, называемый python-tables.(Ссылка в ответе ниже)

HDF5 делает работу на 1000% лучше, экономя тонны и тонны данных.Чтение / изменение данных из 200 миллионов строк, тем не менее, является трудной задачей, поэтому стоит решить следующую проблему.


Я создаю дерево каталогов, содержащее множество подкаталогов и файлов.Существует около 10 миллионов файлов, распределенных по сотням тысяч каталогов.Каждый файл находится в 32 подкаталогах.

У меня есть скрипт Python, который создает эту файловую систему и читает и записывает эти файлы.Проблема в том, что когда я получаю более миллиона файлов, методы чтения и записи становятся очень медленными.

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

def addInFile(path, scoreToAdd):
    num = scoreToAdd
    try:
        shutil.copyfile(path, '/tmp/tmp.txt')
        fp = open('/tmp/tmp.txt', 'r')
        num += int(fp.readlines()[0])
        fp.close()
    except:
        pass
    fp = open('/tmp/tmp.txt', 'w')
    fp.write(str(num))
    fp.close()
    shutil.copyfile('/tmp/tmp.txt', path)
  • Реляционные базы данных кажутся слишком медленными для доступа к этим данным, поэтому я выбрал подход с файловой системой.
  • Ранее я пытался выполнить консольные команды linux для них, но это было намного медленнее.
  • Сначала я копирую файл во временный файл, затем получаю доступ / изменяю его, а затем копирую обратно, потому что я обнаружил, что это быстрее, чем прямой доступ к файлу.
  • Помещение всех файлов в 1 каталог (в формате reiserfs) вызвало слишком большое замедление при доступе к файлам.

Я думаю, что причина замедления в том, что существует множество файлов.Выполнение этой функции выполнялось 1000 раз менее чем за секунду ... но теперь она достигает 1 минуты.

Как вы предлагаете мне это исправить?Могу ли я изменить структуру дерева каталогов?

Все, что мне нужно, - это быстрый доступ к каждому файлу в этом огромном пуле файлов *

Ответы [ 7 ]

6 голосов
/ 14 июня 2010

Я знаю, что это не прямой ответ на ваш вопрос, но это прямое решение вашей проблемы.

Вам нужно исследовать, используя что-то вроде HDF5 .Он предназначен только для типа иерархических данных с миллионами отдельных точек данных.

Вам действительно повезло, потому что есть отличные привязки Python для HDF5, называемые pytables .Я использовал его очень похожим образом и имел огромный успех.

6 голосов
/ 13 июня 2010

Два предложения:

Первое , структура, которая включает в себя 32-глубинное вложение подкаталогов, по своей сути ошибочна.Предполагая, что у вас действительно «около 10 миллионов файлов», одного уровня подкаталогов должно быть абсолютно достаточно (при условии, что вы используете современную файловую систему).

Second : вы говорите, что имеете «около 10»миллион файлов "и что каждый файл" содержит целую строку ".Предполагая, что это 32-разрядные целые числа, и вы сохраняете их напрямую, а не в виде строк, это составляет общий размер набора данных 40 МБ (10 М файлов * 4 байта на файл).Предполагая, что каждое имя файла имеет длину 32 байта, добавьте еще 320 МБ для «ключей» к этим данным.

Таким образом, вы сможете легко поместить весь набор данных в память .Я предлагаю сделать именно это и оперировать данными, хранящимися в основной памяти.И если нет какой-либо причины, вам нужна сложная структура каталогов, я также предлагаю хранить данные в одном файле.

5 голосов
/ 13 июня 2010

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

Если вы выполняете много операций ввода-вывода, вы также можете просто добавить больше оборудования для решения проблемы и использовать твердотельные накопители или хранить все данные в ОЗУ (явным образом или кэшируя). Только с жесткими дисками у вас нет шансов добиться хорошей производительности в этом сценарии.

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

2 голосов
/ 13 июня 2010

Устранение всех этих подкаталогов занимает время. Вы перегружаете файловую систему.

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

/parent/00/01/02/03/04/05/06/07
       /08/09/0A/0B/0C/0D/0E/0F
       /10/11/12/13/14/15/16/17
       /18/19/1A/1B/1C/1D/1E/1F.txt

... вы можете создать файл с таким путем:

/parent/00_01_02_03_04_05_06_07_
        08_09_0A_0B_0C_0D_0E_0F_
        10_11_12_13_14_15_16_17_
        18_19_1A_1B_1C_1D_1E_1F.txt

... конечно, у вас все еще будет проблема, потому что теперь все ваши десять миллионов файлов будут в одном каталоге, и, по моему опыту (NTFS), каталог с более чем несколькими тысячами файлов в это все еще завышает налоговую нагрузку на файловую систему.

Вы можете придумать гибридный подход:

/parent/00_01_02_03/04_05_06_07
       /08_09_0A_0B/0C_0D_0E_0F
       /10_11_12_13/14_15_16_17
       /18_19_1A_1B/1C_1D_1E_1F.txt

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

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

На скольких уровнях вы должны создать иерархию каталогов? В моем небольшом примере я преобразовал вашу 32-уровневую иерархию в 8-уровневую иерархию, но после некоторого тестирования вы можете выбрать немного другое отображение. Это действительно зависит от ваших данных и от того, насколько равномерно эти пути распределены в пространстве комбинаторных решений. Вам необходимо оптимизировать решение с двумя ограничениями:

1) Минимизируйте количество каталогов, которые вы создаете, зная, что каждый каталог становится INODE в базовой файловой системе, и создание слишком большого количества из них приведет к перегрузке файловой системы.

2) Минимизируйте количество файлов в каждом каталоге, зная, что слишком большое количество файлов в каталоге (по моему опыту, более 1000) перегружает файловую систему.

Есть еще одно соображение, о котором следует помнить: пространство памяти на дисках адресуется и распределяется с использованием «блоков». Если вы создаете файл, размер которого меньше минимального размера блока, он, тем не менее, занимает весь блок, тратя впустую дисковое пространство. В NTFS эти блоки определяются своим «размером кластера» (который частично определяется общим размером тома) и обычно по умолчанию равен 4 КБ:

http://support.microsoft.com/kb/140365

Таким образом, если вы создадите файл только с одним байтом данных, он все равно будет занимать 4 КБ дискового пространства, тратя впустую 4095 байт.

В своем примере вы сказали, что у вас есть около 10 миллионов файлов с объемом данных около 1 ГБ. Если это правда, то каждый из ваших файлов имеет длину около 100 байт. При размере кластера 4096 коэффициент использования пространства составляет около 98%.

Если это вообще возможно, попробуйте объединить некоторые из этих файлов. Я не знаю, какие данные они содержат, но если это текстовый формат, вы можете попробовать сделать что-то вроде этого:

[id:01_23_45_67_89_AB_CD_EF]
lorem ipsum dolor sit amet consectetur adipiscing elit
[id:fe_dc_ba_98_76_54_32_10]
ut non lorem quis quam malesuada lacinia
[id:02_46_81_35_79_AC_DF_BE]
nulla semper nunc id ligula eleifend pulvinar

... и так далее, и тому подобное. Может показаться, что вы теряете пространство со всеми этими подробными заголовками, но для диска это гораздо более эффективная стратегия, чем наличие отдельных файлов для всех этих маленьких фрагментов. Этот небольшой пример использовал ровно 230 байтов (включая переводы строк) для трех записей, поэтому вы можете попытаться поместить около шестнадцати записей в каждый файл (помня, что гораздо лучше иметь чуть меньше 4096 байтов на файл, чем чуть больше чем 4096, тратя впустую весь дополнительный блок диска).

Во всяком случае, удачи!

2 голосов
/ 13 июня 2010
  1. Диск ограничен количеством байтов, которые он может читать / записывать в секунду, а также количеством операций , которые он может выполнить за секунду.
  2. Пока ваши небольшие файлыкэшированные операции выполняются значительно быстрее, чем с некэшированными файлами.

Похоже, что вы решаете обе проблемы,

  • делает слишком много операций ввода-вывода
  • не хватает кеша

Я бы предложил пересмотреть структуру, которую вы используете, и использовать файлы меньшего размера.Храните в minf (как правило), чем операция ввода-вывода менее 128 КБ. Стоимость выполнения более или менее равна I / O в 1 байт!

1 голос
/ 13 июня 2010

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

РЕДАКТИРОВАТЬ: предыдущая версия имеет ошибку, когда количество цифр становится меньше, чем текущее количество цифр (например, если вы вычитаете или складываете отрицательное число); эта версия исправляет это, результат хронометража почти не изменяется

def addInFile(path, scoreToAdd):
    try:
        fp = open(path, 'r+')
    except IOError as e:
        print e
    else:
        num = str(scoreToAdd + int(fp.read()))
        fp.seek(0)
        fp.write(num)
        fp.truncate(len(num))
    finally:
        fp.close()

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

def addInFile(path, scoreToAdd):
    try:
        orig = open(path, 'r')
        tmp = open('/home/lieryan/junks/tmp.txt', 'w')
    except IOError as e:
        print e
    else:
        num = int(orig.read())
        tmp.write(str(scoreToAdd + num))
    finally:
        orig.close()
        tmp.close()
    try:
        # make sure /tmp/ and path is in the same partition
        # otherwise the fast shutil.move become a slow shutil.copy
        shutil.move(path, '/home/lieryan/junks/backup.txt')
        shutil.move('/home/lieryan/junks/tmp.txt', path)
        os.remove('/home/lieryan/junks/backup.txt')
    except (IOError, shutil.Error) as e:
        print e

также, не используйте голые исключения.

В качестве альтернативы, как насчет группировки всех 256 файлов из нижнего листа в один больший файл? Тогда вы можете прочитать несколько чисел за один раз, в одном кэше. И если вы использовали файл с фиксированной шириной, то вы можете быстро использовать seek (), чтобы добраться до любой записи в файле в O (1).

Некоторые тайминги, записывающие 1000 раз в один и тот же файл:

  • Ваш оригинальный подход: 1.87690401077
  • Мой первый подход (открыть с помощью rw +): 0.0926730632782
  • Мой второй подход, скопируйте в тот же раздел: 0.464048147202

(все функции не проверены на пути обработки ошибок)

0 голосов
/ 30 августа 2015

Если вы пользуетесь Linux и получили большой объем памяти (64 ГБ +), попробуйте tmpfs, он действительно работает как смонтированный диск, и вам не нужно менять код или покупать другой SSD.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...