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 ячейки
* ПРЕДЫДУЩего сегмента флашатся в кэш (lag-1: к этому моменту сегмент уже
* полностью просканирован); последний сегмент флашится после цикла index_lines.
* g_cur_merged переживает переносы внутри одного параграфа; g_next_merged
* выставляет index_lines перед блоком (рудимент эпохи поэтапной миграции —
* сейчас все типы строит merge, так что флаг всегда 1). */
* g_ncells_at_space — снимок длины на последнем пробеле для усечения буфера
* при переносе строки (отбрасываем уехавшее за границу слово). */
static uint8_t g_cells[MAX_CACHE_LINE_LEN * 2];
static uint8_t g_ncells = 0; /* ячеек в текущем сегменте */
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)
{
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++;
/* Флаш ячеек ПРЕДЫДУЩего сегмента (lag-1): он полностью собран форвард-сканером. */
if (n_lines >= 2) seg_commit_cells((uint16_t)(n_lines - 2));
g_cur_merged = g_next_merged; /* статус нового сегмента (сейчас всегда 1) */
g_ncells = 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 (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 (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;
}
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;
}
}
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;
}
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;
}
}
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;
}
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;
}
}
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;
}
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 = tgt_full;
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;
}
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_style = line_style;
if (g_cur_merged) g_ncells_at_space = g_ncells;
g_ncells_at_space = g_ncells;
seg_col++; q++; prev_ch = ' ';
} 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;
}
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;
if (wrap_at >= q_end || wrap_at == cur_seg_off) break;
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);
base = ATTR_TEXT; /* continuation заголовка → PLAIN */
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++;
continue;
}
if (g_cur_merged) gc_put('`', styles_map[line_style]);
gc_put('`', styles_map[line_style]);
seg_col++;
if (!soft_break) q++; else q++;
prev_ch = '`';
@@ -1166,7 +1166,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
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;
if (!soft_break) q += 2; else q++;
prev_ch = '*';
@@ -1185,7 +1185,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
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;
if (!soft_break) q += 2; else q++;
prev_ch = '~';
@@ -1206,7 +1206,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
continue;
}
}
if (g_cur_merged) gc_put(ch, styles_map[line_style]);
gc_put(ch, styles_map[line_style]);
seg_col++;
if (!soft_break) q++; else q++;
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;
if (tgt > SCREEN_W) tgt = SCREEN_W;
/* 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;
if (!soft_break) q++; else q++;
prev_ch = ' ';
continue;
}
if (ch == ' ') {
if (g_cur_merged) gc_put(' ', styles_map[line_style]);
gc_put(' ', styles_map[line_style]);
if (soft_break) {
last_space = q;
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++;
q++;
}
if (g_cur_merged) g_ncells_at_space = g_ncells; /* снимок для усечения */
g_ncells_at_space = g_ncells; /* снимок для усечения */
prev_ch = ' ';
} else {
if (g_cur_merged) gc_put(ch, styles_map[line_style]);
gc_put(ch, styles_map[line_style]);
seg_col++;
if (!soft_break) q++; else q++;
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;
if (wrap_at >= file_size || wrap_at == cur_seg_off) break;
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);
/* Префикс continuation-сегмента (как render в cont-блоке): список —
* col пробелов; цитата — отступ + маркер 0xB3 + пробел; plain — без префикса. */
if (g_cur_merged && ckind == CK_LIST) {
if (ckind == CK_LIST) {
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;
uint8_t qsp = 0;
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;
while (p < file_size && n_lines < max_lines) {
g_next_merged = 0; /* по умолчанию блок не мигрирован → render; мигрированные типы выставят 1 */
if ((n_lines & 15) == 0)
spinner_tick();
@@ -1305,7 +1304,6 @@ static void index_lines(void)
if (p < file_size) p++;
/* Серия пустых строк схлопывается в одну визуальную строку. */
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);
set_blank_cur();
}
@@ -1315,7 +1313,6 @@ static void index_lines(void)
/* ---- fenced code-блок (граница или тело) ---- */
if (is_fence_raw(p)) {
in_block = !in_block;
g_next_merged = 1; /* S5: граница ``` — пустой буфер (len 0) */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
set_nowrap_cur();
while (p < file_size && fb(p) != '\n') p++;
@@ -1323,12 +1320,11 @@ static void index_lines(void)
continue;
}
if (in_block) {
g_next_merged = 1; /* S5: тело кода — verbatim ATTR_TEXT_CODE */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
set_nowrap_cur();
set_code_cur();
set_hscroll_cur(); /* код скроллится целым блоком */
if (g_cur_merged) {
{
uint8_t ccx = 0;
uint32_t pp = p;
while (pp < file_size && fb(pp) != '\n') {
@@ -1352,10 +1348,9 @@ static void index_lines(void)
/* ---- горизонтальный разделитель ---- */
if (is_hr_raw(p)) {
g_next_merged = 1; /* S5: HR — 80×0xC4 ATTR_HR */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
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++;
if (p < file_size) p++;
continue;
@@ -1363,11 +1358,10 @@ static void index_lines(void)
/* ---- строка таблицы (| ... |): один nowrap-сегмент на исходную строку ---- */
if (is_table_raw(p)) {
g_next_merged = 1; /* S5: таблица — inline-парсинг без переноса */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
set_nowrap_cur();
set_hscroll_cur(); /* таблица скроллится целым блоком */
if (g_cur_merged) {
{
uint32_t line_end = p;
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);
@@ -1390,7 +1384,6 @@ static void index_lines(void)
case LK_H3: hbase = ATTR_TEXT_TITLE3; break;
default: hbase = ATTR_TEXT_TITLE4; break;
}
g_next_merged = 1; /* S2: заголовки строит merge */
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
uint32_t line_end = content_off;
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;
uint32_t q = content_off;
g_next_merged = 1; /* S3: списки строит merge */
emit_seg(p, line_style, CK_LIST, 0);
/* Префикс ПЕРВОГО сегмента: отступ + маркер + пробел (как render). */
if (g_cur_merged) {
{
uint32_t qq = p, qmax = p + SCREEN_W;
if (kind == LK_ULIST) {
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;
uint32_t q = content_off;
g_next_merged = 1; /* S4: цитаты строит merge */
emit_seg(p, line_style, CK_QUOTE, 0);
/* Префикс ПЕРВОГО сегмента: отступ + маркер 0xB3 + пробел (как render LK_QUOTE). */
if (g_cur_merged) {
{
uint32_t qq = p, qmax = p + SCREEN_W;
while (qq + 1 < content_off && qq < qmax && fb(qq) == ' ') qq++;
gc_fill(' ', ATTR_TEXT, (uint8_t)(qq - p));
@@ -1483,7 +1474,6 @@ static void index_lines(void)
/* ---- Обычный текстовый параграф: склейка через soft-break ---- */
{
uint8_t line_style = INIT_STYLE_PLAIN;
g_next_merged = 1; /* S1: параграфы строит merge */
emit_seg(p, line_style, CK_PLAIN, 0);
uint32_t q = p;