Как эффективно анализировать файл bigdata json (wikidata) на C++?

у меня есть один файл json, который составляет около 36 ГБ (поступает из wikidata), и я хочу получить доступ к нему более эффективно. В настоящее время я использую rapidjsons SAX-style API на C++, но разбор всего файла занимает на моей машине около 7415200 МС (=120 минут). Я хочу получить доступ к объектам json внутри этого файла в соответствии с одним из двух первичных ключей ("имя" или "ключ сущности" - > т. е. "переполнение стека" или "Q549037"), которые находятся внутри объекта json. Это означает, что я должен проанализировать весь файл в настоящее время в худший случай.

Итак, я подумал о двух подходах:

  • разделение большого файла на миллиарды небольших файлов-с именем файла, указывающим имя / ключ сущности (т. е. Q549037.в JSON / Stack_Overflow.json или Q549037#Stack_Overflow.json) - > не уверен в перегрузке в хранилище
  • создание какого-то индекса из первичных ключей в ftell() позицию в файле. Построение индекса должно занять около 120 минут (например, разбор сейчас), но доступ должен быть быстрее
    • т. е. использовать что-то вроде двух std::unorderedmap (снова могут возникнуть проблемы с памятью)
    • индексные файлы-создайте два файла: один с записями, отсортированными по имени, и один, отсортированный по ключу сущности (создание этих файлов, вероятно, займет гораздо больше времени из-за сортировки)

какова наилучшая практика для такой проблемы? Какой подход мне следует придерживаться? Есть другие идеи?

4 ответов


Я думаю, что проблема производительности не из-за разбора. Использование API Sax RapidJSON уже должно дать хорошую производительность и память. Если вам нужно получить доступ ко всем значениям в JSON, это уже может быть лучшим решением.

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

вам понадобится некоторый механизм индексирования. Это можно сделать с помощью File position. Если данные в позициях также являются допустимыми JSON, вы можете искать и передавать их в RapidJSON для разбора этого значения JSON (RapidJSON может остановить разбор, когда полный JSON анализируется, по kParseStopWhenDoneFlag).

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

обратите внимание, что JSON-это формат обмена. Он не был разработан для быстрых индивидуальных запросов к большим данным.


Update: недавно я обнаружил, что есть проект полу-индекс что может удовлетворить ваши потребности.


напишите свой собственный парсер JSON, минимизирующий распределение и перемещение данных. Также канава мульти символ для прямой ANSI. Однажды я написал синтаксический анализатор XML для анализа Xml-файлов 4GB. Я попробовал MSXML и Xerces, у обоих были незначительные утечки памяти,которые при использовании этого количества данных фактически заканчивались. Мой парсер фактически остановит выделение памяти, как только достигнет максимального уровня вложенности.


ваше определение проблема не позволяет дать точный ответ.

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

Если вы интенсивно используете данные wikia, почему бы не преобразовать их в более управляемый формат?

должно быть легко автоматизировать определение БД, которое соответствует формату ваших записей, и преобразовать большой кусок JSON в DB записывает раз и навсегда.

вы можете остановить преобразование БД в любой момент (т. е. сохранить каждый блок JSON как обычный текст или уточнить его дальше).
В минимальном случае вы получите таблицу БД с индексированными по имени и ключу записями.
Конечно, менее грязно, чем использование вашей файловой системы в качестве базы данных (путем создания миллионов файлов с именем name+key) или написания выделенного кода для поиска записей.

Это, вероятно, сэкономит вам много диска пространство тоже, так как внутреннее хранилище БД обычно более эффективно, чем простое текстовое представление.


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

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

Я использую простую программу python для анализа данных, которые он в основном запускает несколько регулярных выражений на каждой строке; один, чтобы найти строки, содержащие <title>...</title> поэтому я знаю, какая статья Википедии это и еще несколько, чтобы найти пространство имен и теги математики. Он может обрабатывать файл 160MB за 13 секунд, поэтому может сделать все 36GB менее чем за час.

этот код создает текстовые файлы только с данными, которые меня интересуют. Если вам интересно код

import sys
import re

dump = len(sys.argv)>1 and sys.argv[1]=='-d'
titleRE = re.compile('<title>(.*)</title>')
nsRE = re.compile('<ns>(.*)</ns>')
mathRE = re.compile('&lt;/?math(.*?)&gt;')
pageEndRE = re.compile('</page>')
supOc = 0
supCc = 0
subOc = 0
subCc = 0

title =""
attr = ""
ns = -1
inEqn = 0
for line in sys.stdin:
    m = titleRE.search(line)
    if m :
        title = m.group(1)
        expression = ""
        if dump : print line
        inEqn = 0
    m = nsRE.search(line)
    if m :
        ns = m.group(1)
    start = 0
    pos = 0
    m = mathRE.search(line,pos)
    while m :
        if m.group().startswith('&lt;math'):
            attr = m.group(1)
            start = m.end()
            pos = start
            expression = ""
            inEqn = 1
        if m.group() == '&lt;/math&gt;' :
            end = m.start()
            expression = '    '.join([expression,line[start:end]])
            print title,'\t',attr,'\t',expression.lstrip().replace('&lt;','<').replace('&gt;',
'>').replace('&amp;','&')
            pos = m.end()
            expression = ""
            start = 0
            inEqn = 0
        m = mathRE.search(line,pos)
    if start > 0 :
        expression = line[start:].rstrip()
    elif inEqn :
        expression = '    '.join([expression,line.rstrip()])

Извините, если это немного загадочно, но это не было для общественного потребления. Пример вывода составляет

Arithmetic mean         a_1,\ldots,a_n.
Arithmetic mean         A
Arithmetic mean         A=\frac{1}{n}\sum_{i=1}^{n} a_i
Arithmetic mean         \bar{x}

каждая строка имеет название статьи и уравнения latex. Это уменьшает данные, с которыми мне нужно работать, до более управляемых 500k. Я не уверен, что такая стратегия будет работать для вашего приложения.

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