diff --git a/devel/0126.md b/devel/0126.md new file mode 100644 index 0000000000..7e327c25f1 --- /dev/null +++ b/devel/0126.md @@ -0,0 +1,53 @@ +# [0126] 优化 smart_font 字体加载性能 + +## 1 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 + +## 2 任务相关的代码文件 +- `src/Graphics/Fonts/smart_font.cpp` +- `src/Graphics/Fonts/smart_font.hpp` +- `tests/Graphics/Fonts/smart_font_test.cpp` + +## 3 如何测试 + +### 3.1 确定性测试(单元测试) +```bash +xmake b smart_font_test +xmake r smart_font_test +``` + +### 3.2 非确定性测试(文档验证) +```bash +# 构建并运行字体相关测试,确保所有用例通过 +xmake b smart_font_test && xmake r smart_font_test +``` + +## 4 如何提交 + +提交前执行以下最少步骤: + +```bash +xmake b smart_font_test +xmake r smart_font_test +``` + +## 5 What + +通过缓存避免 smart_font 字体加载过程中的重复函数调用,优化性能。 + +1. 添加 `maybe_initialize_font` 辅助函数,避免对已初始化的子字体重复调用 `initialize_font` +2. 在构造函数中缓存 `trimmed_tokenize(family, ",")` 结果(`family_tokens`),避免 `resolve(string c)` 和 `is_italic_prime` 中重复分割字符串 +3. 在构造函数中缓存 `logical_font` 结果(`given_font`),避免 `resolve(c, fam, attempt)` 中重复计算 +4. 缓存 `is_italic_prime` 的结果,避免对同一字体的斜体引号判断重复计算 +5. 新增 `test_performance` 和 `test_math_performance` 回归测试 + +## 6 Why + +在字体加载过程中,通过添加日志发现很多函数会被重复调用。例如解析数学模式下 24 个希腊字母时,`initialize_font` 被调用了 21 次,虽然大部分调用的子字体已经初始化过。`trimmed_tokenize(family, ",")` 和 `logical_font` 也在每次 `resolve` 时被重复计算,造成不必要的性能开销。 + +## 7 How + +- `maybe_initialize_font(int nr)`:在调用 `initialize_font` 前检查 `fn[nr]` 是否已初始化,只有未初始化时才执行实际的字体创建逻辑 +- `family_tokens`:在 `smart_font_rep` 构造函数中将 `trimmed_tokenize(family, ",")` 结果保存为成员变量,供 `resolve(string c)` 和 `is_italic_prime` 直接使用 +- `given_font`:在构造函数中将 `logical_font(family, variant, series, rshape)` 结果保存为成员变量,供 `resolve(c, fam, attempt)` 中的 `is_wanted` 调用直接使用 +- `is_italic_prime` 缓存:添加 `italic_prime_cached` 和 `italic_prime_result` 成员变量,首次计算后缓存结果 diff --git a/src/Graphics/Fonts/smart_font.cpp b/src/Graphics/Fonts/smart_font.cpp index a1d4e68481..4d222cc41a 100644 --- a/src/Graphics/Fonts/smart_font.cpp +++ b/src/Graphics/Fonts/smart_font.cpp @@ -625,6 +625,9 @@ smart_font_rep::smart_font_rep (string name, font base_fn, font err_fn, fn[SUBFONT_MAIN] = adjust_subfont (base_fn); fn[SUBFONT_ERROR]= adjust_subfont (err_fn); this->copy_math_pars (base_fn); + family_tokens = trimmed_tokenize (family, ","); + given_font = logical_font (family, variant, series, rshape); + italic_prime_cached= false; if (shape == "mathitalic" || shape == "mathupright" || shape == "mathshape") { if (is_math_family (mfam)) { rshape= "right"; @@ -632,7 +635,7 @@ smart_font_rep::smart_font_rep (string name, font base_fn, font err_fn, else { tree key= tuple ("math", mfam, variant, series, rshape); int nr = sm->add_font (key, REWRITE_MATH); - initialize_font (nr); + maybe_initialize_font (nr); this->copy_math_pars (fn[nr]); fn[SUBFONT_MAIN]= fn[nr]; } @@ -645,7 +648,7 @@ smart_font_rep::smart_font_rep (string name, font base_fn, font err_fn, if (math_kind == 2) this->copy_math_pars (base_fn); else { italic_nr= sm->add_font (tuple ("fast-italic"), REWRITE_NONE); - initialize_font (italic_nr); + maybe_initialize_font (italic_nr); this->copy_math_pars (fn[italic_nr]); } (void) sm->add_font (tuple ("special"), REWRITE_SPECIAL); @@ -836,7 +839,7 @@ smart_font_rep::advance (string s, int& pos, string& r, int& nr) { } if (count == 1 && nr != -1 && fn_index == nr) { - if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr); + maybe_initialize_font (nr); if (!fn[nr]->supports (s (start, end))) break; pos= end; } @@ -851,7 +854,7 @@ smart_font_rep::advance (string s, int& pos, string& r, int& nr) { } r= s (start, pos); if (nr < 0) return; - if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr); + maybe_initialize_font (nr); if (sm->fn_rewr[nr] != REWRITE_NONE) r= rewrite (r, sm->fn_rewr[nr]); } if (DEBUG_VERBOSE) { @@ -911,10 +914,9 @@ smart_font_rep::resolve (string c, string fam, int attempt) { } array a= trimmed_tokenize (fam, "="); if (N (a) >= 2) { - array given= logical_font (family, variant, series, rshape); - fam = a[1]; - array b = tokenize (a[0], " "); - bool ok = is_wanted (c, fam, b, given); + fam = a[1]; + array b = tokenize (a[0], " "); + bool ok= is_wanted (c, fam, b, given_font); if (!ok) { return -1; } @@ -930,7 +932,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) { if (cfn->supports (c)) { tree key= tuple ("subfont", fam); int nr = sm->add_font (key, REWRITE_NONE); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } } @@ -957,7 +959,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) { if (cfn->supports (c) || c == "<#200B>") { tree key= tuple (fam, variant, series, rshape, "1"); int nr = sm->add_font (key, REWRITE_NONE); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } } @@ -965,33 +967,33 @@ smart_font_rep::resolve (string c, string fam, int attempt) { if (fam == "roman" && range == "greek") { tree key= tuple ("greek", fam, variant, series, rshape); int nr = sm->add_font (key, REWRITE_NONE); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } if (fam == "roman" && range == "latin") { tree key= tuple ("latin", fam, variant, series, rshape); int nr = sm->add_font (key, REWRITE_NONE); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } if (is_math_family (fam)) { tree key= tuple ("math", fam, variant, series, rshape); int nr = sm->add_font (key, REWRITE_MATH); - initialize_font (nr); + maybe_initialize_font (nr); if (fn[nr]->supports (rewrite (c, REWRITE_MATH))) return sm->add_char (key, c); } if ((fam == "roman" || fam == "cyrillic") && N (c) > 1) { tree key= tuple ("cyrillic", fam, variant, series, rshape); int nr = sm->add_font (key, REWRITE_CYRILLIC); - initialize_font (nr); + maybe_initialize_font (nr); if (fn[nr]->supports (rewrite (c, REWRITE_CYRILLIC))) return sm->add_char (key, c); } if (c == "<#3000>") { tree key= tuple ("ignore"); int nr = sm->add_font (key, REWRITE_IGNORE); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } if (N (c) == 7 && starts (c, "add_font (key, REWRITE_POOR_BBB); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } } if (starts (c, "")) { tree key= tuple ("it"); int nr = sm->add_font (key, REWRITE_ITALIC); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } if (fam == mfam && !is_italic_font (mfam)) { @@ -1025,7 +1027,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) { if (virtually_defined (c, emu_names[i])) { tree key= tuple ("emulate", emu_names[i]); int nr = sm->add_font (key, REWRITE_NONE); - initialize_font (nr); + maybe_initialize_font (nr); if (fn[nr]->supports (c)) return sm->add_char (key, c); } } @@ -1045,7 +1047,7 @@ smart_font_rep::resolve (string c, string fam, int attempt) { if (cfn->supports (c) || c == "<#200B>") { tree key= tuple (fam, v, series, rshape, as_string (a)); int nr = sm->add_font (key, REWRITE_NONE); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } } @@ -1056,12 +1058,18 @@ smart_font_rep::resolve (string c, string fam, int attempt) { bool smart_font_rep::is_italic_prime (string c) { if (c != "'" && c != "`") return false; - array a= trimmed_tokenize (family, ","); - string s= "<#2B9>"; + if (italic_prime_cached) return italic_prime_result; + string s= "<#2B9>"; if (c == "`") s= ""; - for (int i= 0; i < N (a); i++) - if (resolve (s, a[i], 1) >= 0) return false; - return true; + bool result= true; + for (int i= 0; i < N (family_tokens); i++) + if (resolve (s, family_tokens[i], 1) >= 0) { + result= false; + break; + } + italic_prime_cached= true; + italic_prime_result= result; + return result; } extern bool has_poor_rubber; @@ -1079,7 +1087,7 @@ smart_font_rep::resolve_rubber (string c, string fam, int attempt) { if (goal == "." || goal == "") { tree key= tuple ("ignore"); int nr = sm->add_font (key, REWRITE_IGNORE); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } if (has_poor_rubber) { @@ -1101,7 +1109,7 @@ smart_font_rep::resolve_rubber (string c, string fam, int attempt) { if (bnr >= 0 && bnr < N (fn) && !is_nil (fn[bnr])) { tree key= tuple ("rubber", as_string (bnr)); int nr = sm->add_font (key, REWRITE_NONE); - initialize_font (nr); + maybe_initialize_font (nr); // cout << fn[nr]->res_name << " supports " << c << "? " // << fn[nr]->supports (c) << LF; if (fn[nr]->supports (c)) return sm->add_char (key, c); @@ -1138,7 +1146,7 @@ smart_font_rep::resolve (string c) { debug_fonts << "Main subfont of " << cork_to_utf8 (c) << " is " << fn[SUBFONT_MAIN]->res_name << LF; } - array a= trimmed_tokenize (family, ","); + array a= family_tokens; // Special handling for emoji characters - bypass font-family restrictions string range= get_unicode_range (c); @@ -1167,7 +1175,7 @@ smart_font_rep::resolve (string c) { if (!is_nil (cfn) && cfn->supports (c)) { tree key= tuple ("emoji-font", parts[1]); int nr = sm->add_font (key, REWRITE_NONE); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } } @@ -1179,14 +1187,14 @@ smart_font_rep::resolve (string c) { if (upc != "" && fn[SUBFONT_MAIN]->supports (upc)) { tree key= tuple ("up"); int nr = sm->add_font (key, REWRITE_UPRIGHT); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } string ugc= substitute_upright_greek (c); if (ugc != "" && fn[SUBFONT_MAIN]->supports (ugc)) { tree key= tuple ("upright-greek"); int nr = sm->add_font (key, REWRITE_UPRIGHT_GREEK); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } if (is_greek (c) && use_italic_greek (a) && shape != "mathupright") { @@ -1194,7 +1202,7 @@ smart_font_rep::resolve (string c) { if (gc != "" && fn[SUBFONT_MAIN]->supports (gc)) { tree key= tuple ("italic-greek"); int nr = sm->add_font (key, REWRITE_ITALIC_GREEK); - initialize_font (nr); + maybe_initialize_font (nr); return sm->add_char (key, c); } // cout << "Found " << c << " in greek\n"; @@ -1371,7 +1379,7 @@ smart_font_rep::initialize_font (int nr) { fn[nr] = poor_bbb_font (sfn, pw, ph, 1.5 * pw); } else if (a[0] == "rubber" && N (a) == 2 && is_int (a[1])) { - initialize_font (as_int (a[1])); + maybe_initialize_font (as_int (a[1])); fn[nr]= adjust_subfont (rubber_font (fn[as_int (a[1])])); // fn[nr]= adjust_subfont (rubber_unicode_font (fn[as_int (a[1])])); } @@ -1388,6 +1396,11 @@ smart_font_rep::initialize_font (int nr) { } } +void +smart_font_rep::maybe_initialize_font (int nr) { + if (N (fn) <= nr || is_nil (fn[nr])) initialize_font (nr); +} + static int get_ex (string family, string variant, string series, string shape, int attempt) { diff --git a/src/Graphics/Fonts/smart_font.hpp b/src/Graphics/Fonts/smart_font.hpp index b31a5b4b89..9e44ede7b8 100644 --- a/src/Graphics/Fonts/smart_font.hpp +++ b/src/Graphics/Fonts/smart_font.hpp @@ -99,8 +99,12 @@ struct smart_font_rep : font_rep { int math_kind; int italic_nr; - array fn; - smart_map sm; + array fn; + smart_map sm; + array family_tokens; + array given_font; + bool italic_prime_cached; + bool italic_prime_result; smart_font_rep (string name, font base_fn, font err_fn, string family, string variant, string series, string shape, double sz, @@ -117,6 +121,7 @@ struct smart_font_rep : font_rep { int resolve_rubber (string c, string fam, int attempt); int resolve (string c); void initialize_font (int nr); + void maybe_initialize_font (int nr); int adjusted_dpi (string fam, string var, string ser, string sh, int att); font make_rubber_font (font base) override; diff --git a/tests/Graphics/Fonts/smart_font_test.cpp b/tests/Graphics/Fonts/smart_font_test.cpp index 31f9396db5..7024836304 100644 --- a/tests/Graphics/Fonts/smart_font_test.cpp +++ b/tests/Graphics/Fonts/smart_font_test.cpp @@ -38,6 +38,8 @@ private slots: void test_get_right_slope (); void test_latin_modern_math_italic_greek (); void test_cursor_position_iii (); + void test_performance (); + void test_math_performance (); }; void @@ -161,5 +163,30 @@ TestSmartFont::test_cursor_position_iii () { STACK_DELETE_ARRAY (xpos); } +void +TestSmartFont::test_performance () { + font fn= smart_font ("sys-chinese", "rm", "medium", "right", 10, 600); + + // Trigger a lot of character resolutions with repeated characters + string long_text= "The quick brown fox jumps over the lazy dog. " + "The quick brown fox jumps over the lazy dog. " + "The quick brown fox jumps over the lazy dog."; + metric ex; + fn->get_extents (long_text, ex); +} + +void +TestSmartFont::test_math_performance () { + font fn= + smart_font ("Latin Modern Math", "rm", "medium", "mathitalic", 10, 600); + + // Trigger math character resolutions + string math_text= "" + "" + ""; + metric ex; + fn->get_extents (math_text, ex); +} + QTEST_MAIN (TestSmartFont) #include "smart_font_test.moc"