mdview2: снять рудиментные merge-гварды (11→10с)

После полной миграции на merge флаги g_cur_merged/g_next_merged всегда
равны 1 (все типы блоков строит форвард-сканер). Убраны сами флаги и ~30
гвардов `if (g_cur_merged)` в горячем посимвольном цикле — это и небольшое
ускорение (ветка на символ), и чистка.

Проверено замером: инлайн gc_put (макрос/inline) в этот регистро-нагруженный
цикл, наоборот, ЗАМЕДЛЯЕТ (~11→13с) из-за роста спиллов — оставлен функцией.
Вывод: per-char микрооптимизации здесь исчерпаны (asm-fb не помог, инлайн
навредил, снятие гвардов дало ~1с).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 16:06:48 +03:00
parent 92c0a9a4a9
commit de11882d16
+31 -41
View File
@@ -780,15 +780,16 @@ static uint8_t marker_visible_col(uint8_t kind, uint32_t p_start, uint32_t conte
* по файлу — без отдельного второго скана. На каждом emit_seg ячейки * по файлу — без отдельного второго скана. На каждом emit_seg ячейки
* ПРЕДЫДУЩего сегмента флашатся в кэш (lag-1: к этому моменту сегмент уже * ПРЕДЫДУЩего сегмента флашатся в кэш (lag-1: к этому моменту сегмент уже
* полностью просканирован); последний сегмент флашится после цикла index_lines. * полностью просканирован); последний сегмент флашится после цикла index_lines.
* g_cur_merged переживает переносы внутри одного параграфа; g_next_merged * g_ncells_at_space — снимок длины на последнем пробеле для усечения буфера
* выставляет index_lines перед блоком (рудимент эпохи поэтапной миграции — * при переносе строки (отбрасываем уехавшее за границу слово). */
* сейчас все типы строит merge, так что флаг всегда 1). */
static uint8_t g_cells[MAX_CACHE_LINE_LEN * 2]; static uint8_t g_cells[MAX_CACHE_LINE_LEN * 2];
static uint8_t g_ncells = 0; /* ячеек в текущем сегменте */ static uint8_t g_ncells = 0; /* ячеек в текущем сегменте */
static uint8_t g_ncells_at_space = 0; /* снимок g_ncells на последнем пробеле (для усечения при переносе) */ static uint8_t g_ncells_at_space = 0; /* снимок g_ncells на последнем пробеле (для усечения при переносе) */
static uint8_t g_cur_merged = 0; /* строящийся сегмент построен merge'ем (всегда 1) */
static uint8_t g_next_merged = 0; /* статус следующего сегмента (ставит index_lines) */
/* Пишет одну (char,attr)-ячейку в g_cells, молча игнорируя переполнение
* MAX_CACHE_LINE_LEN. Оставлена ФУНКЦИЕЙ намеренно: инлайн в сильно
* регистро-нагруженный скан-цикл на Z80/SDCC увеличивает спиллы и замедляет
* (проверено замером — макро-инлайн дал регрессию ~11→13с). */
static void gc_put(char ch, uint8_t attr) static void gc_put(char ch, uint8_t attr)
{ {
if (g_ncells < MAX_CACHE_LINE_LEN) { if (g_ncells < MAX_CACHE_LINE_LEN) {
@@ -838,7 +839,6 @@ static void emit_seg(uint32_t off, uint8_t style, uint8_t ckind, uint8_t cont)
n_lines++; n_lines++;
/* Флаш ячеек ПРЕДЫДУЩего сегмента (lag-1): он полностью собран форвард-сканером. */ /* Флаш ячеек ПРЕДЫДУЩего сегмента (lag-1): он полностью собран форвард-сканером. */
if (n_lines >= 2) seg_commit_cells((uint16_t)(n_lines - 2)); if (n_lines >= 2) seg_commit_cells((uint16_t)(n_lines - 2));
g_cur_merged = g_next_merged; /* статус нового сегмента (сейчас всегда 1) */
g_ncells = 0; /* буфер ячеек под новый сегмент */ g_ncells = 0; /* буфер ячеек под новый сегмент */
g_ncells_at_space = 0; g_ncells_at_space = 0;
} }
@@ -929,7 +929,7 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
if (ch == '`') { if (ch == '`') {
if (line_style == INIT_STYLE_PLAIN) { line_style = INIT_STYLE_CODE; q++; continue; } if (line_style == INIT_STYLE_PLAIN) { line_style = INIT_STYLE_CODE; q++; continue; }
if (line_style == INIT_STYLE_CODE) { line_style = INIT_STYLE_PLAIN; q++; continue; } if (line_style == INIT_STYLE_CODE) { line_style = INIT_STYLE_PLAIN; q++; continue; }
if (g_cur_merged) gc_put('`', emph_to_attr(line_style, base)); gc_put('`', emph_to_attr(line_style, base));
seg_col++; q++; prev_ch = '`'; continue; seg_col++; q++; prev_ch = '`'; continue;
} }
if (ch == '*' && (q + 1) < q_end && fb(q + 1) == '*') { if (ch == '*' && (q + 1) < q_end && fb(q + 1) == '*') {
@@ -941,7 +941,7 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
line_style = INIT_STYLE_PLAIN; q += 2; continue; line_style = INIT_STYLE_PLAIN; q += 2; continue;
} }
} }
if (g_cur_merged) { gc_put('*', emph_to_attr(line_style, base)); gc_put('*', emph_to_attr(line_style, base)); } { gc_put('*', emph_to_attr(line_style, base)); gc_put('*', emph_to_attr(line_style, base)); }
seg_col += 2; q += 2; prev_ch = '*'; continue; seg_col += 2; q += 2; prev_ch = '*'; continue;
} }
if (ch == '~' && (q + 1) < q_end && fb(q + 1) == '~') { if (ch == '~' && (q + 1) < q_end && fb(q + 1) == '~') {
@@ -953,7 +953,7 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
line_style = INIT_STYLE_PLAIN; q += 2; continue; line_style = INIT_STYLE_PLAIN; q += 2; continue;
} }
} }
if (g_cur_merged) { gc_put('~', emph_to_attr(line_style, base)); gc_put('~', emph_to_attr(line_style, base)); } { gc_put('~', emph_to_attr(line_style, base)); gc_put('~', emph_to_attr(line_style, base)); }
seg_col += 2; q += 2; prev_ch = '~'; continue; seg_col += 2; q += 2; prev_ch = '~'; continue;
} }
if (ch == '*' || ch == '_') { if (ch == '*' || ch == '_') {
@@ -967,7 +967,7 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
line_style = INIT_STYLE_PLAIN; q++; continue; line_style = INIT_STYLE_PLAIN; q++; continue;
} }
} }
if (g_cur_merged) gc_put(ch, emph_to_attr(line_style, base)); gc_put(ch, emph_to_attr(line_style, base));
seg_col++; q++; prev_ch = ch; continue; seg_col++; q++; prev_ch = ch; continue;
} }
if (ch == '\t') { if (ch == '\t') {
@@ -975,17 +975,17 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
uint8_t tgt_full = (uint8_t)((seg_col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP); uint8_t tgt_full = (uint8_t)((seg_col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP);
uint8_t tgt = tgt_full; uint8_t tgt = tgt_full;
if (tgt > SCREEN_W) tgt = SCREEN_W; if (tgt > SCREEN_W) tgt = SCREEN_W;
if (g_cur_merged) gc_fill(' ', emph_to_attr(line_style, base), (uint8_t)(tgt_full - old_sc)); gc_fill(' ', emph_to_attr(line_style, base), (uint8_t)(tgt_full - old_sc));
seg_col = tgt; q++; prev_ch = ' '; continue; seg_col = tgt; q++; prev_ch = ' '; continue;
} }
if (ch == ' ') { if (ch == ' ') {
if (g_cur_merged) gc_put(' ', emph_to_attr(line_style, base)); gc_put(' ', emph_to_attr(line_style, base));
last_space = q + 1; last_space = q + 1;
last_space_style = line_style; last_space_style = line_style;
if (g_cur_merged) g_ncells_at_space = g_ncells; g_ncells_at_space = g_ncells;
seg_col++; q++; prev_ch = ' '; seg_col++; q++; prev_ch = ' ';
} else { } else {
if (g_cur_merged) gc_put(ch, emph_to_attr(line_style, base)); gc_put(ch, emph_to_attr(line_style, base));
seg_col++; q++; prev_ch = ch; seg_col++; q++; prev_ch = ch;
} }
if (!nowrap && seg_col >= SCREEN_W) { if (!nowrap && seg_col >= SCREEN_W) {
@@ -993,7 +993,7 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
uint32_t wrap_at = use_ls ? last_space : q; uint32_t wrap_at = use_ls ? last_space : q;
if (wrap_at >= q_end || wrap_at == cur_seg_off) break; if (wrap_at >= q_end || wrap_at == cur_seg_off) break;
uint8_t wrap_style = use_ls ? last_space_style : line_style; uint8_t wrap_style = use_ls ? last_space_style : line_style;
if (g_cur_merged && use_ls) g_ncells = g_ncells_at_space; if (use_ls) g_ncells = g_ncells_at_space;
emit_seg(wrap_at, wrap_style, ckind, 1); emit_seg(wrap_at, wrap_style, ckind, 1);
base = ATTR_TEXT; /* continuation заголовка → PLAIN */ base = ATTR_TEXT; /* continuation заголовка → PLAIN */
seg_col = col; last_space = 0xFFFFFFFFu; prev_ch = ' '; q = wrap_at; seg_col = col; last_space = 0xFFFFFFFFu; prev_ch = ' '; q = wrap_at;
@@ -1147,7 +1147,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
if (!soft_break) q++; if (!soft_break) q++;
continue; continue;
} }
if (g_cur_merged) gc_put('`', styles_map[line_style]); gc_put('`', styles_map[line_style]);
seg_col++; seg_col++;
if (!soft_break) q++; else q++; if (!soft_break) q++; else q++;
prev_ch = '`'; prev_ch = '`';
@@ -1166,7 +1166,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
continue; continue;
} }
} }
if (g_cur_merged) { gc_put('*', styles_map[line_style]); gc_put('*', styles_map[line_style]); } { gc_put('*', styles_map[line_style]); gc_put('*', styles_map[line_style]); }
seg_col += 2; seg_col += 2;
if (!soft_break) q += 2; else q++; if (!soft_break) q += 2; else q++;
prev_ch = '*'; prev_ch = '*';
@@ -1185,7 +1185,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
continue; continue;
} }
} }
if (g_cur_merged) { gc_put('~', styles_map[line_style]); gc_put('~', styles_map[line_style]); } { gc_put('~', styles_map[line_style]); gc_put('~', styles_map[line_style]); }
seg_col += 2; seg_col += 2;
if (!soft_break) q += 2; else q++; if (!soft_break) q += 2; else q++;
prev_ch = '~'; prev_ch = '~';
@@ -1206,7 +1206,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
continue; continue;
} }
} }
if (g_cur_merged) gc_put(ch, styles_map[line_style]); gc_put(ch, styles_map[line_style]);
seg_col++; seg_col++;
if (!soft_break) q++; else q++; if (!soft_break) q++; else q++;
prev_ch = ch; prev_ch = ch;
@@ -1218,14 +1218,14 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
uint8_t tgt = tgt_full; uint8_t tgt = tgt_full;
if (tgt > SCREEN_W) tgt = SCREEN_W; if (tgt > SCREEN_W) tgt = SCREEN_W;
/* render заполняет до НЕкапнутого tgt_full (по cc); для plain cc==seg_col */ /* render заполняет до НЕкапнутого tgt_full (по cc); для plain cc==seg_col */
if (g_cur_merged) gc_fill(' ', styles_map[line_style], (uint8_t)(tgt_full - old_sc)); gc_fill(' ', styles_map[line_style], (uint8_t)(tgt_full - old_sc));
seg_col = tgt; seg_col = tgt;
if (!soft_break) q++; else q++; if (!soft_break) q++; else q++;
prev_ch = ' '; prev_ch = ' ';
continue; continue;
} }
if (ch == ' ') { if (ch == ' ') {
if (g_cur_merged) gc_put(' ', styles_map[line_style]); gc_put(' ', styles_map[line_style]);
if (soft_break) { if (soft_break) {
last_space = q; last_space = q;
last_space_style = line_style; last_space_style = line_style;
@@ -1236,10 +1236,10 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
seg_col++; seg_col++;
q++; q++;
} }
if (g_cur_merged) g_ncells_at_space = g_ncells; /* снимок для усечения */ g_ncells_at_space = g_ncells; /* снимок для усечения */
prev_ch = ' '; prev_ch = ' ';
} else { } else {
if (g_cur_merged) gc_put(ch, styles_map[line_style]); gc_put(ch, styles_map[line_style]);
seg_col++; seg_col++;
if (!soft_break) q++; else q++; if (!soft_break) q++; else q++;
prev_ch = ch; prev_ch = ch;
@@ -1249,13 +1249,13 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
uint32_t wrap_at = use_ls ? last_space : q; uint32_t wrap_at = use_ls ? last_space : q;
if (wrap_at >= file_size || wrap_at == cur_seg_off) break; if (wrap_at >= file_size || wrap_at == cur_seg_off) break;
uint8_t wrap_style = use_ls ? last_space_style : line_style; uint8_t wrap_style = use_ls ? last_space_style : line_style;
if (g_cur_merged && use_ls) g_ncells = g_ncells_at_space; /* усечь до пробела */ if (use_ls) g_ncells = g_ncells_at_space; /* усечь до пробела */
emit_seg(wrap_at, wrap_style, ckind, 1); emit_seg(wrap_at, wrap_style, ckind, 1);
/* Префикс continuation-сегмента (как render в cont-блоке): список — /* Префикс continuation-сегмента (как render в cont-блоке): список —
* col пробелов; цитата — отступ + маркер 0xB3 + пробел; plain — без префикса. */ * col пробелов; цитата — отступ + маркер 0xB3 + пробел; plain — без префикса. */
if (g_cur_merged && ckind == CK_LIST) { if (ckind == CK_LIST) {
gc_fill(' ', ATTR_TEXT, col); gc_fill(' ', ATTR_TEXT, col);
} else if (g_cur_merged && ckind == CK_QUOTE) { } else if (ckind == CK_QUOTE) {
uint32_t content_off2 = para_start + col; uint32_t content_off2 = para_start + col;
uint8_t qsp = 0; uint8_t qsp = 0;
while (qsp + 2 < content_off2 && qsp < SCREEN_W && fb(para_start + qsp) == ' ') qsp++; while (qsp + 2 < content_off2 && qsp < SCREEN_W && fb(para_start + qsp) == ' ') qsp++;
@@ -1293,7 +1293,6 @@ static void index_lines(void)
uint32_t p = 0; uint32_t p = 0;
while (p < file_size && n_lines < max_lines) { while (p < file_size && n_lines < max_lines) {
g_next_merged = 0; /* по умолчанию блок не мигрирован → render; мигрированные типы выставят 1 */
if ((n_lines & 15) == 0) if ((n_lines & 15) == 0)
spinner_tick(); spinner_tick();
@@ -1305,7 +1304,6 @@ static void index_lines(void)
if (p < file_size) p++; if (p < file_size) p++;
/* Серия пустых строк схлопывается в одну визуальную строку. */ /* Серия пустых строк схлопывается в одну визуальную строку. */
if (n_lines == 0 || !(cur_rec.flags & IF_BLANK)) { if (n_lines == 0 || !(cur_rec.flags & IF_BLANK)) {
g_next_merged = 1; /* S5: blank — пустой буфер (len 0) */
emit_seg(p, INIT_STYLE_PLAIN, CK_PLAIN, 0); emit_seg(p, INIT_STYLE_PLAIN, CK_PLAIN, 0);
set_blank_cur(); set_blank_cur();
} }
@@ -1315,7 +1313,6 @@ static void index_lines(void)
/* ---- fenced code-блок (граница или тело) ---- */ /* ---- fenced code-блок (граница или тело) ---- */
if (is_fence_raw(p)) { if (is_fence_raw(p)) {
in_block = !in_block; in_block = !in_block;
g_next_merged = 1; /* S5: граница ``` — пустой буфер (len 0) */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0); emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
set_nowrap_cur(); set_nowrap_cur();
while (p < file_size && fb(p) != '\n') p++; while (p < file_size && fb(p) != '\n') p++;
@@ -1323,12 +1320,11 @@ static void index_lines(void)
continue; continue;
} }
if (in_block) { if (in_block) {
g_next_merged = 1; /* S5: тело кода — verbatim ATTR_TEXT_CODE */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0); emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
set_nowrap_cur(); set_nowrap_cur();
set_code_cur(); set_code_cur();
set_hscroll_cur(); /* код скроллится целым блоком */ set_hscroll_cur(); /* код скроллится целым блоком */
if (g_cur_merged) { {
uint8_t ccx = 0; uint8_t ccx = 0;
uint32_t pp = p; uint32_t pp = p;
while (pp < file_size && fb(pp) != '\n') { while (pp < file_size && fb(pp) != '\n') {
@@ -1352,10 +1348,9 @@ static void index_lines(void)
/* ---- горизонтальный разделитель ---- */ /* ---- горизонтальный разделитель ---- */
if (is_hr_raw(p)) { if (is_hr_raw(p)) {
g_next_merged = 1; /* S5: HR — 80×0xC4 ATTR_HR */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0); emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
set_nowrap_cur(); set_nowrap_cur();
if (g_cur_merged) gc_fill(0xC4, ATTR_HR, SCREEN_W); gc_fill(0xC4, ATTR_HR, SCREEN_W);
while (p < file_size && fb(p) != '\n') p++; while (p < file_size && fb(p) != '\n') p++;
if (p < file_size) p++; if (p < file_size) p++;
continue; continue;
@@ -1363,11 +1358,10 @@ static void index_lines(void)
/* ---- строка таблицы (| ... |): один nowrap-сегмент на исходную строку ---- */ /* ---- строка таблицы (| ... |): один nowrap-сегмент на исходную строку ---- */
if (is_table_raw(p)) { if (is_table_raw(p)) {
g_next_merged = 1; /* S5: таблица — inline-парсинг без переноса */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0); emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
set_nowrap_cur(); set_nowrap_cur();
set_hscroll_cur(); /* таблица скроллится целым блоком */ set_hscroll_cur(); /* таблица скроллится целым блоком */
if (g_cur_merged) { {
uint32_t line_end = p; uint32_t line_end = p;
while (line_end < file_size && fb(line_end) != '\n') line_end++; while (line_end < file_size && fb(line_end) != '\n') line_end++;
(void)inline_scan(p, line_end, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 1); (void)inline_scan(p, line_end, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 1);
@@ -1390,7 +1384,6 @@ static void index_lines(void)
case LK_H3: hbase = ATTR_TEXT_TITLE3; break; case LK_H3: hbase = ATTR_TEXT_TITLE3; break;
default: hbase = ATTR_TEXT_TITLE4; break; default: hbase = ATTR_TEXT_TITLE4; break;
} }
g_next_merged = 1; /* S2: заголовки строит merge */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0); emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
uint32_t line_end = content_off; uint32_t line_end = content_off;
while (line_end < file_size && fb(line_end) != '\n') line_end++; while (line_end < file_size && fb(line_end) != '\n') line_end++;
@@ -1406,10 +1399,9 @@ static void index_lines(void)
uint8_t line_style = INIT_STYLE_PLAIN; uint8_t line_style = INIT_STYLE_PLAIN;
uint32_t q = content_off; uint32_t q = content_off;
g_next_merged = 1; /* S3: списки строит merge */
emit_seg(p, line_style, CK_LIST, 0); emit_seg(p, line_style, CK_LIST, 0);
/* Префикс ПЕРВОГО сегмента: отступ + маркер + пробел (как render). */ /* Префикс ПЕРВОГО сегмента: отступ + маркер + пробел (как render). */
if (g_cur_merged) { {
uint32_t qq = p, qmax = p + SCREEN_W; uint32_t qq = p, qmax = p + SCREEN_W;
if (kind == LK_ULIST) { if (kind == LK_ULIST) {
while (qq + 2 < content_off && qq < qmax && fb(qq) == ' ') qq++; while (qq + 2 < content_off && qq < qmax && fb(qq) == ' ') qq++;
@@ -1462,10 +1454,9 @@ static void index_lines(void)
uint8_t line_style = INIT_STYLE_PLAIN; uint8_t line_style = INIT_STYLE_PLAIN;
uint32_t q = content_off; uint32_t q = content_off;
g_next_merged = 1; /* S4: цитаты строит merge */
emit_seg(p, line_style, CK_QUOTE, 0); emit_seg(p, line_style, CK_QUOTE, 0);
/* Префикс ПЕРВОГО сегмента: отступ + маркер 0xB3 + пробел (как render LK_QUOTE). */ /* Префикс ПЕРВОГО сегмента: отступ + маркер 0xB3 + пробел (как render LK_QUOTE). */
if (g_cur_merged) { {
uint32_t qq = p, qmax = p + SCREEN_W; uint32_t qq = p, qmax = p + SCREEN_W;
while (qq + 1 < content_off && qq < qmax && fb(qq) == ' ') qq++; while (qq + 1 < content_off && qq < qmax && fb(qq) == ' ') qq++;
gc_fill(' ', ATTR_TEXT, (uint8_t)(qq - p)); gc_fill(' ', ATTR_TEXT, (uint8_t)(qq - p));
@@ -1483,7 +1474,6 @@ static void index_lines(void)
/* ---- Обычный текстовый параграф: склейка через soft-break ---- */ /* ---- Обычный текстовый параграф: склейка через soft-break ---- */
{ {
uint8_t line_style = INIT_STYLE_PLAIN; uint8_t line_style = INIT_STYLE_PLAIN;
g_next_merged = 1; /* S1: параграфы строит merge */
emit_seg(p, line_style, CK_PLAIN, 0); emit_seg(p, line_style, CK_PLAIN, 0);
uint32_t q = p; uint32_t q = p;