Python: медленное чтение и запись для миллионов небольших файлов

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

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


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

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

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

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 для них, но это было медленнее.
  • Сначала я копирую файл во временный файл, затем обращаюсь к нему, а затем копирую его, потому что обнаружил, что это быстрее, чем прямой доступ к файлу.
  • Ввод всех файлов в один каталог (в формате reiserfs) вызвал слишком много замедления при доступе к файлам.

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

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

Мне нужно только быстро получить доступ к каждому файлу в этом огромном пуле файлов *

Ответы

Ответ 1

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

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

Вы ДЕЙСТВИТЕЛЬНО в удаче, потому что есть великолепные привязки Python для HDF5, называемые pytables. Я использовал его очень похожим образом и имел огромный успех.

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

Похоже, вы нажимаете на обе проблемы,

  • выполнение слишком большого количества операций ввода/вывода
  • исчерпание кеша

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

Ответ 5

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

EDIT: предыдущая версия имеет ошибку, когда количество цифр становится меньше текущего количества цифр (например, если вы вычитаете или добавили отрицательное число); эта версия исправляет это, результат синхронизации практически не изменяется

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
  • Мой первый подход (open with rw +): 0.0926730632782
  • Мой второй подход, скопируйте его в тот же раздел: 0.464048147202

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

Ответ 6

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

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

/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 эти блоки определяются их "размером кластера" (который частично определяется общим размером тома) и обычно по умолчанию равен 4kB:

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, тратя весь дополнительный блок диска).

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

Ответ 7

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