Pull to refresh

Comments 18

При надлежащем форматировании исходных строк, можно остановиться на обыкновенных 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, в котором это могло бы выглядеть как-то так:


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

Просто кровь из глазок…
Полагаю, это 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;
};

Only those users with full accounts are able to leave comments. Log in, please.