Комментарии 19
Форматируем наши строки в
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()
}
Разумеется, конкретные цифры можно найти регулярным выражением и закешировать, многократно переиспользуя.
Данный вариант должен оказаться наиболее быстрым из возможных, требующим наименьшее количество дополнительной памяти.
Когда сам занимался наиболее быстрым парсингом, пришёл к заключению, что наиболее эффективными оказываются фиксированные длины, без какого либо парсинга и регулярных выражений.
Например, вот что придумалось:
У нас есть смещение до цифр первого ключа. После него, ищем ближайший пробел, вырезаем полученную подстроку с цифрами, и прибавляем к нему смещение до следующего ключа, вроде:
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уется. Конкретные цифры смещений опять таки можно вычислить заранее и закешировать.
gist.githubusercontent.com/Kilor/8c4fee60a94073c1accc6bdd167e9990/raw/8b2918e5eb55c1e61034e80adb10554551434718/buffers.txt
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)
, {}
);
};
Ну и имейте ввиду, что вас на Хабре дети читают. Начитаются ещё тут оптимизаций с зубодробительными регулярками, да как понесут в свой проект на радость тимлиду.
ну это, надо ж как-то в узде держать своё стремление к овер-инженерингу) даже если это 2077)
Параметр auto_explain.log_format выбирает формат вывода для EXPLAIN. Он может принимать значение text, xml, json и yaml. Значение по умолчанию — text.postgrespro.ru/docs/postgresql/13/auto-explain
Сказать, что последние 3 варианта хоть как-то человекочитаемы — сложно.
Думаю тут стоило бы сделать им мерж-реквест с форматом tree, в котором это могло бы выглядеть как-то так:
Из "золотого" варианта (полнопозиционный .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).
Можно немного доработать и обрабатывать файл(ы) в потоковом режиме.
Можно немного доработать и обрабатывать файл(ы) в потоковом режиме.Но на саму скорость парсинга это никак не влияет.
Вот такой вариант еще чуть быстрее получается на мои тестах — в основном, за счет меньшего количества итераций цикла == сравнений условия выхода из него.
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;
};
Первый парсер на деревне