Как стать автором
Обновить

Комментарии 19

При надлежащем форматировании исходных строк, можно остановиться на обыкновенных substring'ах.
Форматируем наши строки в
Buffers: shared hit=00123 read=00456, local hit=00789 (ограничивая диапазон значений), но зато мы точно знаем что можно сделать так:
obj = {
  "shared-hit": line.substring(21, 26).parseInt(),
  "shared-read": line.substring(32, 37).parseInt(),
  "local-hit": line.substring(49, 54).parseInt()
}

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

Когда сам занимался наиболее быстрым парсингом, пришёл к заключению, что наиболее эффективными оказываются фиксированные длины, без какого либо парсинга и регулярных выражений.
Увы, их невозможно отформатировать «заранее», если не пересобирать сам PostgreSQL — они такими прилетают прямо в логе.
В таком случае да, придётся что-то делать.
Например, вот что придумалось:
У нас есть смещение до цифр первого ключа. После него, ищем ближайший пробел, вырезаем полученную подстроку с цифрами, и прибавляем к нему смещение до следующего ключа, вроде:
let line = "Buffers: shared hit=12345 read=456 local hit=789012"
let a = 21 // длина "Buffers: shared hit="
let b = line.indexOf(" ", a) - 1 // позиция пробела, т.е. конец цифр
let shared_hit = line.substring(a, b).parseInt()
a = b + 6 // длина " read="
b = line.indexOf(" ", a) - 1
let shared_read = line.substring(a, b).parseInt()
...


Технически должно быть быстрее всего, хоть и весьма по-индусски/китайски. И я не уверен что обгонит сишные регулярные выражения, с другой стороны может быть заJITуется. Конкретные цифры смещений опять таки можно вычислить заранее и закешировать.
Еще в таком решении где-то должно быть ветвление и проверка текущего символа/строки — ведь может оказаться сразу вначале shared read=123, без hit.

Уже почти 2k21, а мы о сих пор регулярки руками пишем… Ну что это такое? Пора бы уже использовать билдеры рерулярок. Смотрите:


import { $mol_regexp } from "mol_regexp";
const { optional, repeat_greedy, line_end } = $mol_regexp

const numb = repeat_greedy( $mol_regexp.digit )

const pair = ( scope: string, field: string )=> optional([
    ' ', field, '=',
    { [ `${scope}-${field}` ]: numb },
])

const fields = ( scope: string, names: string[] )=> optional([
    ' ', scope,
    ... names.map( name => pair( scope, name ) ),
    optional(','),
])

const shared = fields( 'shared', [ 'hit', 'read', 'dirtied', 'written' ] )
const local = fields( 'local', [ 'hit', 'read', 'dirtied', 'written' ] )
const temp = fields( 'temp', [ 'read', 'written' ] )

const buffers = $mol_regexp.from([ 'Buffers:', shared, local, temp, line_end ])

console.table([ ... buffers.parse( text ) ])

https://stackblitz.com/edit/typescript-tynbns?file=index.ts


А кто сказал, что мы их руками пишем?.. :)
Но уж приводить в статье еще и генератор для RegExp — только замусоривать, не в том ее смысл.

Я очень сомневаюсь, что подобный код вам генерирует какая-то тулза:


const parseBuffers = line => {
  buffersRE.lastIndex = 8; // 'Buffers:'.length
  return line.matchAll(buffersRE).next().value
    .slice(1)
    .reduce(
      (rv, val, idx) => (val !== undefined && (rv[buffersKeys[idx]] = Number(val)), rv)
    , {}
    );
};

Ну и имейте ввиду, что вас на Хабре дети читают. Начитаются ещё тут оптимизаций с зубодробительными регулярками, да как понесут в свой проект на радость тимлиду.

Уж за 6 строк кода вряд ли успею детей плохому научить, хотя… Пусть уж лучше тут увидят регулярки без прикрас, чем потом state machine на ручных проверках символов пытаются собрать в прод.

ну это, надо ж как-то в узде держать своё стремление к овер-инженерингу) даже если это 2077)

И всё это потому, что логи хранятся в кастомном немашинночитаемом формате, хотя возможно создание такого варианта, который будет ещё и человекочитаем приэтом.
Параметр auto_explain.log_format выбирает формат вывода для EXPLAIN. Он может принимать значение text, xml, json и yaml. Значение по умолчанию — text.
postgrespro.ru/docs/postgresql/13/auto-explain

Сказать, что последние 3 варианта хоть как-то человекочитаемы — сложно.

Думаю тут стоило бы сделать им мерж-реквест с форматом tree, в котором это могло бы выглядеть как-то так:


НЛО прилетело и опубликовало эту надпись здесь
Полагаю, это Elm. Подобный синтаксис характерен для функциональных языков, например для Haskell.

Из "золотого" варианта (полнопозиционный .match(RegExp)) можно выжать ещё порядка 10-15%, заменив .slice(1).reduce() на цикл for():


const buffersRE = /^Buffers:(?:,? shared(?: hit=(\d+))?(?: read=(\d+))?(?: dirtied=(\d+))?(?: written=(\d+))?)?(?:,? local(?: hit=(\d+))?(?: read=(\d+))?(?: dirtied=(\d+))?(?: written=(\d+))?)?(?:,? temp(?: read=(\d+))?(?: written=(\d+))?)?$/;

const buffersKeys = ['shared-hit', 'shared-read', 'shared-dirtied', 'shared-written', 'local-hit', 'local-read', 'local-dirtied', 'local-written', 'temp-read', 'temp-written'];

const parseBuffers = line => {
    const rv = {};
    const match = line.match(buffersRE);
    for (let i = 1, len = match.length; i < len; i++) {
        const val = match[i];
        if (val !== undefined) {
            rv[buffersKeys[i - 1]] = Number(val);
        }
    }
    return rv;
}
Небольшая оптимизация
const buffersKeys   =   ['shared-hit', 'shared-read', 'shared-dirtied', 'shared-written', 'local-hit', 'local-read', 'local-dirtied', 'local-written', 'temp-hit', 'temp-read', 'temp-dirtied', 'temp-written'];
const tmine =   function (line){
    var rv      =   {},
    val         =   0,
    prefixType  =   16,
    keyType     =   16;

    for (let i = 9, l = line.length; i < l; i++){
        if (keyType == 16){
            if (prefixType != 16){
                switch (line[i]){
                    case 'h':
                        keyType =   prefixType;
                        i   +=  3;
                    break;

                    case 'r':
                        keyType =   1 + prefixType;
                        i   +=  4;
                    break;

                    case 'd':
                        keyType =   2 + prefixType;
                        i   +=  7;
                    break;

                    case 'w':
                        keyType =   3 + prefixType;
                        i   +=  7;
                    break;
                }
            } else {
                switch (line[i]){
                    case 's':
                        prefixType  =   0;
                        i   +=  6;
                    break;

                    case 'l':
                        prefixType  =   4;
                        i   +=  5;
                    break;

                    case 't':
                        prefixType  =   8;
                        i   +=  4;
                    break;
                }
            }
        }
        else {
            switch (line[i]){
                case '1':
                    val =   val * 10 + 1;
                break;

                case '2':
                    val =   val * 10 + 2;
                break;

                case '3':
                    val =   val * 10 + 3;
                break;

                case '4':
                    val =   val * 10 + 4;
                break;

                case '5':
                    val =   val * 10 + 5;
                break;

                case '6':
                    val =   val * 10 + 6;
                break;

                case '7':
                    val =   val * 10 + 7;
                break;

                case '8':
                    val =   val * 10 + 8;
                break;

                case '9':
                    val =   val * 10 + 9;
                break;

                case '0':
                    val =   val * 10;
                break;

                case ',':
                    rv[buffersKeys[keyType]]    =   val;
                    val         =   0;
                    prefixType  =   16;
                    keyType     =   16;
                    ++i;
                break;

                default:
                    rv[buffersKeys[keyType]]    =   val;
                    val     =   0;
                    keyType =   16;
                break;
            }
        }
    }

    if (keyType != 16){
        rv[buffersKeys[keyType]]    =   val;
    }

    return rv;
}

Node 12.19.0, Linux 5.7.X: ~8% быстрее вашей HSM на моей машине; 50ms против 54ms; очень большой прирост даёт отсутствие преобразований строк в числа (минус ~30-40ms).
Можно немного доработать и обрабатывать файл(ы) в потоковом режиме.

Можно немного доработать и обрабатывать файл(ы) в потоковом режиме.
Но на саму скорость парсинга это никак не влияет.

Вот такой вариант еще чуть быстрее получается на мои тестах — в основном, за счет меньшего количества итераций цикла == сравнений условия выхода из него.

49ms vs 54ms
const parseBuffers = line => {
  let rv = {};
  let state;
  for (let off = 9 /*'Buffers: '*/, ln = line.length; off < ln; ) {
    if (state === undefined) {
      switch (line.charCodeAt(off)) {
        case 0x73: // s = shared
          state = 0;
          off += 7;
          break;
        case 0x6c: // l = local
          state = 4;
          off += 6;
          break;
        case 0x74: // t = temp
          state = 8;
          off += 5;
          break;
      }
    }

    let key;
    switch (line.charCodeAt(off)) {
      case 0x68: // h = hit
        key = state;
        off += 4;
        break;
      case 0x72: // r = read
        key = state + 1;
        off += 5;
        break;
      case 0x64: // d = dirtied
        key = state + 2;
        off += 8;
        break;
      case 0x77: // w = written
        key = state + 3;
        off += 8;
        break;
    }

    let val = 0;
    for (; off < ln; off++) {
      let ch = line.charCodeAt(off);
      if (ch == 0x2c) { // ','
        state = undefined;
        off++;
        ch = 0x20;
      }
      if (ch == 0x20) { // ' '
        off++;
        break;
      }
      val = val * 10 + ch - 0x30; // '0'
    }

    rv[buffersKeys[key]] = val;
  }
  return rv;
};

Наверное, если еще вместо undefined использовать число для состояния, то ускорится еще на пару миллисекунд, т.к. движок сможет понять, что это int и сделать оптимизации

Зарегистрируйтесь на Хабре, чтобы оставить комментарий