#javascript #node.js #performance #parsing
#javascript #node.js #Производительность #синтаксический анализ
Вопрос:
Возможно, это несколько разных вещей, объединенных в одну, потому что анализируемые данные содержат несколько действий, которые необходимо выполнить с ними, чтобы получить окончательную структуру данных. Формат, с которым я работаю, выглядит следующим образом:
2020-01-01 01:01:01 America/New_York -- 2020-02-02 02:02:02 America/New_York %a - title 1
#tag1 @tag2 #tag3
%meta{data}
%meta2{data1;data2}
contents1
contents2
2020-01-01 01:01:01 America/New_York -- 2020-02-02 02:02:02 America/New_York %b - title 2
#tag
%meta{data}
contents3
contents4
Существует полная дата (включая часовой пояс), разделитель, другая полная дата (с часовым поясом), разделитель, идентификатор (также в определенном формате), разделитель, заголовок произвольной длины, который может включать юникод, несколько фрагментов метаданных в отдельных строках (например, теги), и, наконец, текстовое содержимое произвольной длины с произвольным количеством строк с использованием пробелов, чтобы знать, когда мы начинаем / заканчиваем текстовое содержимое. Чтобы проанализировать это, я просматриваю каждый .codePointAt()
с простым индексом до .length
строки. Я делаю такие вещи, как пропускаю пробелы, пока не найду содержимое, использую символьную математику, чтобы получить целые числа для таких вещей, как даты, как можно быстрее ( s.codePointAt(i) - '0'.charCodeAt(0)
хотя кэширование числового значения ascii равно 0), беру .substring()
s, использую JS-версию joda-time для получения зонального времени даты, проверяю ввод, чтобы сделатьубедитесь, что она в правильном формате (например: проверьте разделители, убедитесь, что мы получаем число в каждой точке кода при разборе int) и т.д.
Но даже при том, что я делаю все возможное, чтобы добиться максимальной производительности моего текущего анализатора, он все равно намного медленнее, чем анализатор, который я написал на Java. Я знаю, что вы можете добиться чрезвычайно высокой производительности JS, и я уверен, что смогу добиться большей производительности, чем та, которую я получаю в настоящее время. Но я недостаточно разбираюсь в JS, чтобы знать, какие методы я могу использовать для достижения наилучшей производительности. Нужно ли мне создавать библиотеку, обтекающую строковые значения, чтобы создавать «подстроки», которые не вызываются .substring
, потому что это будет более высокая производительность, потому что она не будет копировать строковые данные, как .substring
это было бы? Есть ли более быстрый способ перебора / доступа к каждому символу в строке? Можно ли быстрее создавать даты с помощью часового пояса? и т.д.
В настоящее время я читаю текст из файла в файловой системе с помощью nodejs, поэтому, если для node будет доступен метод, который может быть недоступен для JS на основе браузера, дайте мне знать.
РЕДАКТИРОВАТЬ: результат, который я в настоящее время получаю от этого, — это две записи, которые будут выглядеть примерно как следующий объект JS:
[
{
id: "a",
// NOTE: My actual output uses joda-time to get a ZonedDateTime
// that uses the "America/New_York" part to ensure the resulting
// dates are in that time zone and not assuming it's always local
// time or UTC or something.
created: new Date(2020, 1, 1, 1, 1, 1),
updated: new Date(2020, 2, 2, 2, 2, 2),
title: "title 1",
tags: ["#tag1", "@tag2", "#tag3"],
metadata: [
{name: "meta", data: ["data"]},
{name: "meta2", data: ["data1", "data2"]}
],
content: [
"contents1",
"contents2"
]
},
{
id: "b",
created: new Date(2020, 1, 1, 1, 1, 1),
updated: new Date(2020, 2, 2, 2, 2, 2),
title: "title 2",
tags: ["#tag"],
metadata: [
{name: "meta", data: ["data"]}
],
content: [
"contents3",
"contents4"
]
}
]
ТАКЖЕ: имейте в виду, что этот файл может быть произвольно большим. Например, тестовый файл, с которым я работаю, чтобы проверить, насколько хорошо работает мой JS-код, имеет размер 24 МБ и может проанализировать его за ~ 260 миллисекунд (из строки, хранящейся только в памяти), в то время как мой Java-код может проанализировать тестовый файл размером 21 МБ за ~ 120 миллисекунд (при чтении его из файлав цикле, поэтому он будет кэшироваться, но это все равно выше накладных расходов на операцию). (оба измерения времени выполняются в течение нескольких итераций, чтобы гарантировать, что у JIT есть время для прогрева и оптимизации кода)
РЕДАКТИРОВАТЬ РЕДАКТИРОВАТЬ: обновлены показатели производительности. Я думал, что код JS находится на большем расстоянии от кода Java, чем он есть, но он все равно в два раза медленнее, и я хотел бы знать, есть ли способ сделать это быстрее.
Ответ №1:
Совпадение регулярных выражений может сделать это довольно быстро. Если, как в вашем примере, во входном файле нет строк, начинающихся с чисел, отличных от дат, вы можете .split
использовать разделитель ^(?=d)
— начало строки, за которым следует цифра:
const text = `2020-01-01 01:01:01 America/New_York -- 2020-02-02 02:02:02 America/New_York %id - title
#tag
%meta{data}
contents1
contents2
2020-01-01 01:01:01 America/New_York -- 2020-02-02 02:02:02 America/New_York %id - title
#tag
%meta{data}
contents1
contents2`;
const parts = text.split(/^(?=d)/m);
console.log(parts);
При необходимости регулярное выражение можно было бы сделать более точным, но большая точность, вероятно, потребует более сложного шаблона, что может замедлить работу.
Если вы также хотите извлечь вложенное содержимое каждой части, вы можете использовать matchAll
:
const text = `2020-01-01 01:01:01 America/New_York -- 2020-02-02 02:02:02 America/New_York %a - title 1
#tag1 @tag2 #tag3
%meta{data}
%meta2{data1;data2}
contents1
contents2
2020-01-01 01:01:01 America/New_York -- 2020-02-02 02:02:02 America/New_York %b - title 2
#tag
%meta{data}
contents3
contents4`;
for (const match of text.matchAll(/(^d. )n#(. )n(. (?:n%. )*)((?:n .*) )/gm)) {
const dates = match[1].match(/dS S S /g);
console.log('Dates:', dates);
const tags = match[2].split(' ');
console.log('Tag:', tags);
const meta = match[3].split('n');
console.log('Meta:', meta);
const content = match[4].slice(1).split('n').map(s => s.slice(4));
console.log('Content:', content);
}
https://regex101.com/r/IDxylR/1
(^d. )
— Сопоставление и захват строки даты (которая начинается с цифры)
n#
— Сопоставление и захват того, что предшествует тегу
(. )
— Сопоставьте тег
(. (?:n%. )*)
— Сопоставьте и захватите мета-строки, которые начинаются %
сразу после новой строки и охватывают всю строку
n.
— Сопоставьте то, что предшествует содержимому
((?:n .*) )
— Сопоставление содержимого (новая строка, за которой следует пробел, повторяется)
Комментарии:
1. Ах, это моя вина, что я не сказал, что между записями потенциально могут быть новые строки. И я должен прочитать каждую часть каждой записи в структуре данных. например: две даты, заголовок и т. Д. Я не просто использую каждую запись как необработанную строку. РЕДАКТИРОВАТЬ: если есть способ прояснить это в вопросе, дайте мне знать.
2. Я добавил регулярное выражение, которое выглядит так, как вы хотите. Если это не сработает, пожалуйста, включите в вопрос реалистичный пример ввода.
3. Добавлен более детализированный ввод, а также приближение ожидаемого результата, а также некоторые показатели производительности.
4. Кроме того, регулярное выражение, вероятно, будет быстрее, чем время, затраченное на чтение строки.