Files
envi-code/SourceCode/Code2026/inc/PerfCounterLite.h
T
company-desktop 47d7b96cad perf: 给基础体构造加计时,定位到 DrawPipe/DrawCone/DrawLoft 共占 33%
继续追"剩余效率问题"。上一轮已确认 Parasolid 的 93.4% 发生在我们的视口绘制里,
本轮机位下一层:给 GiDrawUtility 的基础体构造函数加计时。

新增 inc/PerfCounterLite.h:header-only 的轻量计数器。
AecRxBase.arx 不链接 CoreLibrary(静态库只被 CoreCommand 链接),拿不到 SectionPerf
的符号,所以单独做一份;只在 Debug + AEC_PERF_COUNTER=1 时生效,Release 零开销。
统计写 %TEMP%\aec_prim_perf.txt(独立文件,避免提前写出 aec_section_perf.txt
让自动化脚本误判"开图完成")。

接入 GiDrawUtility:DrawPipe 三个重载、DrawLoft 两个重载、DrawCone、DrawCylinder。

实测(big1.dwg,93 s 开图 / 81 s CPU):
  DrawPipe(region)      7201 次  13.2 s  平均 1837us  最长 130ms
  DrawLoft(reg)          883 次  5.27 s  平均 5969us
  DrawLoft(curve)        883 次  5.90 s  平均 6679us
  DrawCone               811 次  4.64 s  平均 5717us
  DrawPipe(pts)/Cylinder 2411 次  4.07 s  平均 1688us
去重后约 27 s,占 81 s CPU 的 33%。

结论(详见 perf-primitive-cost.md):
- 7201 次管道构建 vs 约 2260 个管类实体 = 3.2 次/实体,护栏/爬梯/设备细节也在造管道
- 单次成本差一个量级:圆柱 1.7ms 最便宜(已是优化过的快路径),圆锥 5.7ms、放样 6.0~6.7ms 最贵
- 长尾明显:DrawPipe 最长 130ms(平均的 70 倍),存在个别大件
- 都在"首次构建"路径上,没有重复重建,所以只能少建 / 建得更便宜 / 跨实体复用同参数几何

验证:R230 Debug 编译通过,tools/run_acad_perf.ps1 自动开图取数;
截面缓存与回写仍为正常/0(未受本次插桩影响)。
2026-10-28 23:32:22 +08:00

148 lines
3.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// PerfCounterLite.h —— 给 AecRxBase 这类"不链接 CoreLibrary"的模块用的轻量计数器
//
// 为什么不复用 SectionPerf:CoreLibrary 是静态库且只被 CoreCommand 链接,
// AecRxBase.arx 里拿不到它的符号;所以这里做成 header-only。
//
// 行为:
// - 仅在 Debug(NDEBUG 未定义)且 AEC_PERF_COUNTER=1 时开启,Release 零开销
// - 每个 tag 统计:调用次数、累计耗时、平均/最长
// - 每 200 次调用重写一次 %TEMP%\aec_prim_perf.txt(快照)
// —— 故意不写 aec_section_perf.txt:那个文件是自动化脚本判断"开图完成"的信号,
// 提前写会让脚本误判并提前关闭 CAD
#ifndef PERF_COUNTER_LITE_H
#define PERF_COUNTER_LITE_H
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <string>
namespace perf_lite
{
struct Entry
{
Entry() : n(0), us(0), maxus(0) {}
long long n;
long long us;
long long maxus;
};
inline bool Enabled()
{
#ifndef NDEBUG
static int s_nOn = -1;
if (s_nOn < 0)
{
const char* psz = ::getenv("AEC_PERF_COUNTER");
s_nOn = (psz != NULL && psz[0] != '\0' && psz[0] != '0') ? 1 : 0;
}
return s_nOn == 1;
#else
return false;
#endif
}
inline std::map<std::string, Entry>& Stats()
{
static std::map<std::string, Entry> s_map;
return s_map;
}
inline long long QpcNow()
{
LARGE_INTEGER li;
::QueryPerformanceCounter(&li);
return (long long)li.QuadPart;
}
inline long long QpcFreq()
{
static long long s_nFreq = 0;
if (s_nFreq == 0)
{
LARGE_INTEGER li;
if (!::QueryPerformanceFrequency(&li) || li.QuadPart == 0)
s_nFreq = 1;
else
s_nFreq = (long long)li.QuadPart;
}
return s_nFreq;
}
inline void Dump()
{
char szTmp[MAX_PATH + 1];
::memset(szTmp, 0, sizeof(szTmp));
if (::GetTempPathA(MAX_PATH, szTmp) == 0)
return;
std::string sPath(szTmp);
sPath += "aec_prim_perf.txt";
FILE* fp = NULL;
if (fopen_s(&fp, sPath.c_str(), "w") != 0 || fp == NULL)
return;
fprintf(fp, "---- primitive builders (per tag) ----\n");
fprintf(fp, " count total_ms avg_us max_us tag\n");
const std::map<std::string, Entry>& m = Stats();
for (std::map<std::string, Entry>::const_iterator it = m.begin(); it != m.end(); ++it)
{
const Entry& e = it->second;
const double dAvg = e.n ? ((double)e.us / (double)e.n) : 0.0;
fprintf(fp, " %-9lld %10.1f %10.1f %10lld %s\n",
e.n, (double)e.us / 1000.0, dAvg, e.maxus, it->first.c_str());
}
fclose(fp);
}
inline void Add(const char* pszTag, long long nStart)
{
const long long nUs = (QpcNow() - nStart) * 1000000LL / QpcFreq();
Entry& e = Stats()[std::string(pszTag != NULL ? pszTag : "?")];
++e.n;
e.us += nUs;
if (nUs > e.maxus)
e.maxus = nUs;
// 每 200 次落一次快照,开图结束后文件里就是全量统计
static long long s_nTotal = 0;
if (++s_nTotal % 200 == 0)
Dump();
}
// RAII:函数入口构造即可
class Scope
{
public:
explicit Scope(const char* pszTag) : m_pszTag(pszTag), m_nStart(0)
{
if (Enabled())
m_nStart = QpcNow();
}
~Scope()
{
if (m_nStart != 0)
Add(m_pszTag, m_nStart);
}
private:
Scope(const Scope&);
Scope& operator=(const Scope&);
const char* m_pszTag;
long long m_nStart;
};
// 在卸载时再补一次,确保最后的零头也落盘
inline void Flush()
{
if (Enabled())
Dump();
}
}
#endif // PERF_COUNTER_LITE_H