feat(tpc): PROF 输出按编译启动时间分目录,不再清理旧目录

需求(2026-09-29):PROF 按启动时间分组新建文件夹生成,不清理旧的 PROF。

改动:
- Phase2Translator:新增 `StartProfRun()`(在 PROF 根目录下按 `YYYYmmdd_HHMMSS`
  新建本次编译专属子目录,同名补 `_N`;失败回落 PROF 根目录)与
  `GetProfRunDir()`(主进程用已选定目录,worker 用继承的环境变量
  `TPC_PROF_RUN_DIR` 复用同一目录)。原 `_CleanProfFiles()` 删除——它的语义
  是"每次编译删掉 PROF 下全部 *.prof",与"保留历次"直接冲突。
- worker_prof_*.prof 落 `PROF/<启动时间>/`。
- main_prof_{pid}.prof 与 main_time.txt 同样落该目录(Projectrans.py)。
- Config.main 启动时调用 StartProfRun 并打印本次输出目录。

子目录名经环境变量传子进程:ProcessPoolExecutor 用 spawn 起新解释器,进程内
模块态不共享,环境变量随进程继承 ⇒ 同一次编译的所有 worker 落在同一目录,
不会各建各的。
This commit is contained in:
TermiNexus committed 2026-09-29 16:26:44 +08:00
1 parent 467e62a45e
commit 030d0fd149
3 files changed
+66 -26

No files matched your search

+4 -4
View File
@@ -248,10 +248,10 @@ def main() -> None:
if hotspot_sites_file:
_vlog().info(f"调用点热点统计: {hotspot_sites_file}(按真实调用次数降序落盘,含 main 的模块启动时清空一次);每函数计时: {os.path.join(os.path.dirname(hotspot_sites_file), 'hotspot_time.txt')}")
# 每次编译自动清理子进程性能剖析输出(PROF 目录 *.prof,硬编码路径)
from lib.Projectrans.Phase2Translator import _CleanProfFiles, PROF_DIR
_CleanProfFiles()
_vlog().info(f"PROF 目录已清理: {PROF_DIR}")
# 每次编译按启动时间新建独立的 PROF 子目录(保留旧目录,不清理;见
# Phase2Translator.StartProfRun 的说明)。worker 子进程经环境变量复用同一目录。
from lib.Projectrans.Phase2Translator import StartProfRun
_vlog().info(f"PROF 本次输出目录: {StartProfRun()}")
if args.clean:
if output_dir and os.path.exists(output_dir):
+55 -16
View File
@@ -588,27 +588,65 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_
# 子进程性能剖析:主进程 cProfile 只能看到进程池等待(WaitForMultipleObjects/
# as_completed),看不到 worker 进程内部 CPU 分布(vast parse 等热点)。
# 固定启用(不设环境变量开关),worker 每个任务导出 cProfile dump
# (worker_prof_{pid}_{seq}.prof)到 PROF_DIR(D:\Users\TermiNexus\Desktop\TransPyC\PROF),
# 可用 pstats 合并分析。每次编译开始时由 _CleanProfFiles 清理该目录 *.prof。
# (worker_prof_{pid}_{seq}.prof),主进程导出 main_prof_{pid}.prof + main_time.txt。
# 输出落在 PROF_DIR/<启动时间>/ 下的**本次编译专属目录**(2026-09-29 起)。
# ==================================================================
import functools as _functools
import time as _time
# 硬编码 PROF 输出目录(2026-08-18 起不再用 TPC_WORKER_PROF 环境变量控制)
# 硬编码 PROF 根目录(2026-08-18 起不再用 TPC_WORKER_PROF 环境变量控制)
PROF_DIR: str = r'D:\Users\TermiNexus\Desktop\TransPyC\PROF'
# 本次编译的 run 目录:按启动时间在 PROF_DIR 下新建子目录,**不清理旧目录**
# (2026-09-29 用户要求保留历次 profile 以便跨次对比,旧行为是每次编译删掉
# PROF 根目录下全部 *.prof ⇒ 只能看最近一次)。
# 子目录名经环境变量传给 worker:ProcessPoolExecutor 用 spawn 起新解释器,
# 进程内模块态不共享;环境变量随子进程继承 ⇒ 同一次编译的所有 worker 落在
# 同一个 run 目录,不会各建各的目录。
_PROF_RUN_ENV: str = 'TPC_PROF_RUN_DIR'
_prof_run_dir: str | None = None
def _CleanProfFiles() -> None:
"""清理 PROF_DIR 下全部 *.prof(每次编译开始调用)。"""
def StartProfRun() -> str:
"""每次编译开始时调用一次:创建本次编译专属的 PROF 子目录,返回其路径。
目录名 = PROF 根目录下 `YYYYmmdd_HHMMSS`(编译启动时刻);同名(同一秒内
二次启动)补 `_N` 序号。创建失败时回落到 PROF 根目录,保证不因 profiling
影响编译本身。成功后把目录写进环境变量,供 worker 子进程复用。
"""
global _prof_run_dir
if _prof_run_dir:
return _prof_run_dir
_name: str = _time.strftime('%Y%m%d_%H%M%S')
_dir: str = os.path.join(PROF_DIR, _name)
_seq: int = 1
while os.path.isdir(_dir):
_seq += 1
_dir = os.path.join(PROF_DIR, f'{_name}_{_seq}')
try:
if os.path.isdir(PROF_DIR):
for _n in os.listdir(PROF_DIR):
if _n.endswith('.prof'):
try:
os.remove(os.path.join(PROF_DIR, _n))
except OSError:
pass
os.makedirs(_dir, exist_ok=True)
except OSError:
pass
return PROF_DIR
_prof_run_dir = _dir
os.environ[_PROF_RUN_ENV] = _dir
return _dir
def GetProfRunDir() -> str:
"""当前进程写 profile 应落的目录。
主进程 = StartProfRun 选定的 run 目录;worker = 继承来的环境变量指向的目录。
两者都取不到(未先调 StartProfRun,如 worker 被单独拉起)时回落到 PROF 根目录,
宁可文件落在根目录也不丢 dump。
"""
global _prof_run_dir
if _prof_run_dir:
return _prof_run_dir
_env_dir: str = os.environ.get(_PROF_RUN_ENV, '')
if _env_dir:
_prof_run_dir = _env_dir
return _env_dir
return PROF_DIR
def _ProfileWorker(_fn):
@@ -635,9 +673,10 @@ def _ProfileWorker(_fn):
_seq: int = getattr(_wrapper, '_seq', 0) + 1
_wrapper._seq = _seq
try:
if not os.path.isdir(PROF_DIR):
os.makedirs(PROF_DIR, exist_ok=True)
_out: str = os.path.join(PROF_DIR, f'worker_prof_{os.getpid()}_{_seq}.prof')
_out_dir: str = GetProfRunDir()
if not os.path.isdir(_out_dir):
os.makedirs(_out_dir, exist_ok=True)
_out: str = os.path.join(_out_dir, f'worker_prof_{os.getpid()}_{_seq}.prof')
_prof.dump_stats(_out)
except Exception:
pass