继续追"剩余效率问题"。上一轮已确认 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(未受本次插桩影响)。
148 lines
3.4 KiB
C++
148 lines
3.4 KiB
C++
// 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
|