Поиск строки в большом текстовом файле - профилирование различных методов в python

Этот вопрос задавался много раз. Проведя некоторое время, читая ответы, я сделал небольшое профилирование, чтобы опробовать различные методы, упомянутые ранее...

  • У меня есть 600 МБ файл с строками 6 миллионов строк (Пути категорий из проекта DMOZ).
  • Запись в каждой строке уникальна.
  • Я хочу загрузить файл один раз и продолжить поиск для совпадений в данных

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


1) set :
    (i)  data   = set(f.read().splitlines())
    (ii) result = search_str in data   

Время загрузки ~ 10 с, время поиска ~ 0.0 с, использование памяти ~ 1.2 ГБ


2) list :
    (i)  data   = f.read().splitlines()
    (ii) result = search_str in data

Время загрузки ~ 6 с, время поиска ~ 0,36 с, использование памяти ~ 1,2 ГБ


3) mmap :
    (i)  data   = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    (ii) result = data.find(search_str)

Время загрузки ~ 0 с, время поиска ~ 5.4 с, использование памяти ~ NA


4) Hash lookup (using code from @alienhard below):   

Время загрузки ~ 65 с, время поиска ~ 0.0 с, использование памяти ~ 250 МБ


5) File search (using code from @EOL below):   
   with open('input.txt') as f:
       print search_str in f #search_str ends with the ('\n' or '\r\n') as in the file

Время загрузки ~ 0 с, время поиска ~ 3,2 с, использование памяти ~ NA


6) sqlite (with primary index on url): 

Время загрузки ~ 0 с, время поиска ~ 0.0 с, использование памяти ~ NA


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

  • A лучшая альтернатива, например. sqlite?
  • Способы улучшить время поиска с помощью mmap. У меня 64-разрядная настройка. например, цветные фильтры
  • По мере увеличения размера файла до нескольких ГБ, есть ли способ, которым я могу продолжать использовать 'set', например. расколоть его партиями.

[edit 1] P.S. Мне нужно часто искать, добавлять/удалять значения и не использовать одну хэш-таблицу, потому что мне нужно получить измененные значения позже.

Любые комментарии/предложения приветствуются!

[edit 2] Обновление с результатами методов, предложенных в ответах [edit 3] Обновление с помощью результатов sqlite

Решение. Основываясь на всех профилированиях и отзывах, я думаю, что пойду с sqlite. Второй альтернативный метод 4. Один недостаток sqlite заключается в том, что размер базы данных более чем в два раза превышает исходный файл csv с URL-адресами. Это связано с первичным индексом url

Ответ 1

Вариант 1 отличный, если вам нужно запустить много последовательных поисков. Поскольку set внутренне хеш-таблица, это довольно хорошо при поиске. Тем не менее, требуется время для сборки и работает только хорошо, если ваши данные вписываются в ОЗУ.

Вариант 3 хорош для очень больших файлов, потому что у вас достаточно адресного пространства для их сопоставления, а ОС кэширует достаточно данных. Вы выполняете полную проверку; он может стать довольно медленным, как только ваши данные перестанут вписываться в ОЗУ.

SQLite - определенно хорошая идея, если вам нужно несколько поисков в строке, и вы не можете поместить данные в ОЗУ. Загрузите свои строки в таблицу, создайте индекс, и SQLite построит для вас хорошее b-дерево. Дерево может поместиться в ОЗУ, даже если данные нет (это немного похоже на то, что предложили @alienhard), и даже если это не так, сумма, если требуется I/O, значительно ниже. Конечно, вам нужно создать базу данных SQLite на основе дисков. Я сомневаюсь, что SQLite на основе памяти значительно изменит вариант 1.

Ответ 2

Пользовательский поиск хэш-таблицы с внешними строками

Чтобы получить быстрый доступ и снизить потребление памяти, вы можете сделать следующее:

  • для каждой строки вычисляется строковый хэш и добавляется в хеш-таблицу, например, index[hash] = position (не сохраняйте строку). Если есть столкновение, сохраните все позиции файла для этого ключа в списке.
  • чтобы найти строку, вычислить ее хэш и найти ее в таблице. Если ключ найден, прочитайте строку в position из файла, чтобы убедиться, что вы действительно имеете совпадение. Если несколько позиций проверяют каждый, пока не найдете совпадение или нет.

Отредактируйте 1: замените номер строки по позиции (как указано комментатором, очевидно, что нужно фактическое положение, а не номера строк)

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

from collections import namedtuple 
Node = namedtuple('Node', ['pos', 'next'])

def build_table(f, size):
    table = [ None ] * size
    while True:
        pos = f.tell()
        line = f.readline()
        if not line: break
        i = hash(line) % size
        if table[i] is None:
            table[i] = pos
        else:
            table[i] = Node(pos, table[i])
    return table

def search(string, table, f):
    i = hash(string) % len(table)
    entry = table[i]
    while entry is not None:
        pos = entry.pos if isinstance(entry, Node) else entry
        f.seek(pos)
        if f.readline() == string:
            return True
        entry = entry.next if isinstance(entry, Node) else None
    return False

SIZE = 2**24
with open('data.txt', 'r') as f:
    table = build_table(f, SIZE)
    print search('Some test string\n', table, f)

Хэш строки используется только для индексации в таблицу (если мы использовали обычный dict, хэши также будут храниться как ключи). Позиция файла строки сохраняется в указанном индексе. Столкновения разрешаются с помощью цепочки, т.е. Мы создаем связанный список. Однако первая запись никогда не завершается в node (эта оптимизация делает код немного сложнее, но он экономит довольно некоторое пространство).

Для файла с 6 миллионами строк я выбрал хэш-таблицу размером 2 ^ 24. С моими тестовыми данными я получил 933132 коллизий. (Хэш-таблица с половиной размера сопоставима по объему памяти, но привела к большему количеству конфликтов. Поскольку больше коллизий означает больший доступ к файлам для поиска, я предпочел бы использовать большую таблицу.)

Hash table: 128MB (sys.getsizeof([None]*(2**24)))
Nodes:       64MB (sys.getsizeof(Node(None, None)) * 933132)
Pos ints:   138MB (6000000 * 24)
-----------------
TOTAL:      330MB (real memory usage of python process was ~350MB)

Ответ 3

Вы также можете попробовать

with open('input.txt') as f:
    # search_str is matched against each line in turn; returns on the first match:
    print search_str in f

с search_str, заканчивающимся соответствующей последовательностью новой строки ('\n' или '\r\n'). Это должно использовать небольшую память, так как файл читается постепенно. Он также должен быть довольно быстрым, так как читается только часть файла.

Ответ 4

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

В запросах есть время поиска O (m) (где m - длина ключа) также сохраняют много места при сохранении больших словарей или древовидных данных.

Вы также можете хранить части пути на узлах, чтобы уменьшить счет node - это называется Patricia Trie. Но это замедляет поиск по среднему времени сравнения длины строки. Для получения дополнительной информации об реализациях см. Вопрос SO Trie (Prefix Tree) в Python.

В Python Package Index есть несколько реализаций trie, но они не очень хороши. Я написал один в Ruby и Common Lisp, который особенно хорошо подходит для этой задачи - если вы спросите красиво, я мог бы опубликовать его как открытый источник...: -)

Ответ 5

как насчет решения для индексирования текста?

Я бы использовал Lucene в мире Java, но есть движок python под названием Whoosh

https://bitbucket.org/mchaput/whoosh/wiki/Home

Ответ 6

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