Skip to content

Commit 644d482

Browse files
committed
perf: streamline unqualified call paths
1 parent 8a3be54 commit 644d482

5 files changed

Lines changed: 72 additions & 8 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# R14.16 runtime/VM 热点优化报告
2+
3+
## 范围与基线
4+
5+
- 在 R14.15 的递归函数查找优化后,继续使用 `port/linux/performance` 的 10 轮预编译 `fib(20)` 作为 runtime workload。
6+
- Docker `pikadev`、Release `-O3 -g``PIKA_CONFIG_ENABLE=ON``PIKA_OPTIMIZE_SPEED`,临时移除 performance target 的 `-pg` 后用 Callgrind 采样;parser 不在执行循环内。
7+
- 基线为 `3,016,729,750` 条指令。主要 self 热点包括 VM 主循环 8.63%、`hash_time33` 4.94%、`VM_instruction_handler_OPT` 3.58%、`_obj_getObjWithKeepDeepth` 3.43%、参数加载 3.04%,另有 malloc/free 和栈复制成本。
8+
9+
## 根因与实现
10+
11+
- R6.2 已在 `_obj_getObjWithKeepDeepth(..., keepDeepth=1)` 的路径长度断言后增加无点号快速返回,但无限定函数名仍先创建路径栈缓冲并执行完整 `strlen`
12+
- 将无点号 host 快路径提前到缓冲区和长度检查之前。只有包含点号、确实需要路径复制和分词时才检查 `PIKA_PATH_BUFF_SIZE`;对象查找顺序、嵌套路径和临时对象语义不变。
13+
- VM 成功路径只在 frame error 非零时压入 thread error stack;只有 frame 已报错或 thread error stack 非空时进入完整 stack error 检查,避免无错误时重复检查 frame code。
14+
- 将无头文件声明且仅在 `PikaObj.c` 内使用的 `_obj_getMethodArgWithFullPath` 设为 `static`,用于补偿代码体积并释放编译器优化空间;未采用把 `_obj_getMethodArg` 一并内部化的方案,因为其动态指令略高。
15+
16+
## A/B 与资源
17+
18+
- 最终 Callgrind:`3,016,729,750 -> 2,956,509,801`,减少 `60,219,949` 条指令,改善 1.996%。
19+
- 单独的 error fast path 仅减少 437,824 条指令(0.0145%);主要收益来自避免无限定 host 路径的无效长度扫描。
20+
- performance 可执行文件:text `1,465,403 -> 1,465,212`,减少 191 B;data 保持 70,980 B;bss 保持 24,408 B。
21+
- 分配次数保持 1,106,806,缓存命中保持 9,662;未增加常驻 RAM、对象字段、缓存或动态分配。
22+
23+
## 正确性
24+
25+
- 新增 `pikaMain.recursive_call_error_propagation`,验证递归内部未定义名称仍输出 `NameError`、不生成 `result`,并在释放后保持 `pikaMemNow() == 0`
26+
- 定向 GTest:递归正常调用、递归错误传播、单段/嵌套 host 对象路径共 3/3 通过。
27+
- 定向 Valgrind:11,688 allocs / 11,688 frees,退出 0 bytes,0 leaks,0 errors。
28+
- Docker 默认配置(`PIKA_CONFIG_ENABLE=ON`、4096 B VM stack)隔离网络及既定不稳定 REPL 项后,454/454 通过。
29+
- 曾误复用 `PIKA_CONFIG_ENABLE=OFF` 的 512 B stack 构建,测试在请求 528 B 时进入既有 overflow handler;改用全新默认配置构建后全量通过,该错误口径未计入结论。
30+
31+
## 在线基准
32+
33+
R14.15 的 Action/Pages 已成功发布,`gh run benchmark` 按提交 `8a3be5414` 抓到 13 项测量,其中新增 `fibonacci_recursive_20``74.463991 ms/iter`。该项目此前没有 Pages 历史点,因此不声明线上跨提交改善比例;本轮归因使用同二进制 Callgrind A/B。

docs/MDTODO/pikapython-syntax-compatibility.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,7 @@ P0 修复空 import 与 from-import 缺名称在 Suger_import 路径的解析崩
354354
### R14.15 [completed]
355355

356356
增加排除 parser 时间并校验结果的 Fibonacci runtime benchmark,使用仓库现有 gprof/Callgrind 工具定位 fib workload 热点,只实施不改变 VM 大架构且资源总体不膨胀的优化,并完成同配置 A/B、单元测试和 Linux 回归,完成任务后将详细报告写入[任务报告](./details/pikapython-syntax-compatibility/R14.15_Task_Report.md)
357+
358+
### R14.16 [completed]
359+
360+
在上一轮递归调用查找优化基础上重新采样 runtime/VM 热点,选择至少一个由 benchmark 证明且不改变大架构的性能债务完成同配置 A/B;同步探测相关 runtime 错误边界并补单元测试,保持 RAM/Flash 总体不膨胀,CI 仅后台验收不阻塞本轮开发,完成任务后将详细报告写入[任务报告](./details/pikapython-syntax-compatibility/R14.16_Task_Report.md)

port/linux/test/pikaMain-test.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,30 @@ TEST(pikaMain, recursive_global_function_lookup) {
256256
EXPECT_EQ(pikaMemNow(), 0);
257257
}
258258

259+
TEST(pikaMain, recursive_call_error_propagation) {
260+
PikaObj* pikaMain = newRootObj((char*)"pikaMain", New_PikaMain);
261+
pikaVM_run(pikaMain,
262+
(char*)"def recurse(n):\n"
263+
" if n == 0:\n"
264+
" return missing_name\n"
265+
" return recurse(n - 1)\n"
266+
"result = recurse(3)\n");
267+
268+
pika_bool found = pika_false;
269+
for (int i = 0; i < LOG_BUFF_MAX; i++) {
270+
if (strEqu(log_buff[i],
271+
"NameError: name 'missing_name' is not defined\n")) {
272+
found = pika_true;
273+
break;
274+
}
275+
}
276+
EXPECT_TRUE(found);
277+
EXPECT_FALSE(obj_isArgExist(pikaMain, (char*)"result"));
278+
279+
obj_deinit(pikaMain);
280+
EXPECT_EQ(pikaMemNow(), 0);
281+
}
282+
259283
TEST(pikaMain, and_or_not) {
260284
/* init */
261285
g_PikaMemInfo.heapUsedMax = 0;

src/PikaObj.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -656,9 +656,9 @@ Arg* _obj_getMethodArg(PikaObj* obj, char* methodName, Arg* arg_reg) {
656656
return aMethod;
657657
}
658658

659-
Arg* _obj_getMethodArgWithFullPath(PikaObj* obj,
660-
char* methodPath,
661-
Arg* arg_reg) {
659+
static Arg* _obj_getMethodArgWithFullPath(PikaObj* obj,
660+
char* methodPath,
661+
Arg* arg_reg) {
662662
char* methodName = strPointToLastToken(methodPath, '.');
663663
return _obj_getMethodArg(obj, methodName, arg_reg);
664664
}
@@ -935,18 +935,18 @@ static PikaObj* _obj_getObjWithKeepDeepth(PikaObj* self,
935935
char* objPath,
936936
pika_bool* pIsTemp,
937937
int32_t keepDeepth) {
938-
char objPath_buff[PIKA_PATH_BUFF_SIZE];
939-
char* objPath_ptr = objPath_buff;
940938
pika_assert(NULL != objPath);
941939
if ('.' == objPath[0] && '\0' == objPath[1]) {
942940
return self;
943941
}
944-
pika_assert(strGetSize(objPath) < PIKA_PATH_BUFF_SIZE);
945942
#if !PIKA_NANO_ENABLE
946943
if (1 == keepDeepth && NULL == strchr(objPath, '.')) {
947944
return self;
948945
}
949946
#endif
947+
char objPath_buff[PIKA_PATH_BUFF_SIZE];
948+
char* objPath_ptr = objPath_buff;
949+
pika_assert(strGetSize(objPath) < PIKA_PATH_BUFF_SIZE);
950950
strcpy(objPath_buff, objPath);
951951
int32_t token_num = strGetTokenNum(objPath, '.');
952952
PikaObj* objThis = self;

src/PikaVM.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5239,11 +5239,14 @@ static VMParameters* __pikaVM_runByteCodeFrameWithState(
52395239
#endif
52405240
#endif
52415241
// push vm frame error to thread error stack
5242-
if (pikaVMFrame_checkErrorCode(vm) != PIKA_RES_OK) {
5242+
PIKA_RES error_code = pikaVMFrame_checkErrorCode(vm);
5243+
if (error_code != PIKA_RES_OK) {
52435244
pikaVMThread_pushError(vm->vm_thread, &(vm->error));
52445245
}
52455246
// handle error
5246-
if (pikaVMFrame_checkErrorStack(vm) != PIKA_RES_OK) {
5247+
if (error_code != PIKA_RES_OK ||
5248+
(NULL != vm->vm_thread->error_stack &&
5249+
pikaVMFrame_checkErrorStack(vm) != PIKA_RES_OK)) {
52475250
vm->error.line_code = vm->error.code;
52485251
InstructUnit* head_ins_unit = this_ins_unit;
52495252
/* get first ins of a line */

0 commit comments

Comments
 (0)