现象:Debug 构建的 R230 里 ENVIPERFDUMP 报"未知命令",计数器也永远不生效。 根因:cmakelists.txt 第 23~24 行会把 /D_DEBUG 从 CMAKE_CXX_FLAGS_DEBUG 里正则剥掉, 所以即使 --config Debug 编译,_DEBUG 也没有定义。于是 Phase 0 里用 `#if defined(_DEBUG)` 包住的命令注册和 Enabled() 判定整块被编掉—— 已用二进制验证:修复前 arx 里查不到 ENVIPERFDUMP,修复后能查到(UTF-16 字符串)。 修复:改用 NDEBUG。cmakelists.txt 只对 Release 定义 NDEBUG (`$<$<CONFIG:Release>:NDEBUG>`),所以 `#ifndef NDEBUG` 正好等价于"Debug 构建"。 - CoreLibrary/SectionPerfCounter.cpp:计数器启用条件 - CoreCommand/EarthWorm.cpp:ENVIPERFDUMP / ENVIPERFRESET 的函数定义与命令注册 验证:R230 Debug 两个 target 重新编译通过,产物已部署; 脚本自动开图后 ENVIPERFDUMP 正常输出(数据已记录到计划文档 Phase 0 实测结果)。 已知风险:`#ifdef _DEBUG` 在本仓库本来就不生效,rxdebug.cpp 等历史代码同样如此, 本次只修自己新增的两处,没有动别处。
282 lines
6.7 KiB
C++
282 lines
6.7 KiB
C++
#include "stdafx.h"
|
||
#include "SectionPerfCounter.h"
|
||
|
||
#include <windows.h>
|
||
#include <stdlib.h>
|
||
#include <string>
|
||
#include <map>
|
||
#include <set>
|
||
|
||
namespace
|
||
{
|
||
// 单个计数器:次数 + 累计耗时(µs)
|
||
struct Entry
|
||
{
|
||
Entry() : nCount(0), nTotalUs(0), nMaxUs(0) {}
|
||
|
||
unsigned long long nCount;
|
||
unsigned long long nTotalUs;
|
||
unsigned long long nMaxUs;
|
||
};
|
||
|
||
// 唯一 hash 表的上限:正常图纸几十到几百个唯一截面,
|
||
// 万一文本各不相同(说明缓存没意义)也不让内存无限涨
|
||
const size_t kMaxUniqueHash = 50000;
|
||
|
||
struct Stats
|
||
{
|
||
Stats()
|
||
: bInited(false), bEnabled(false)
|
||
, nRegionTexts(0), nUniqueHashes(0), nSimulatedHits(0), bHashTableOverflow(false)
|
||
, nTempFiles(0), nTempFileBytes(0)
|
||
, nWriteBacks(0)
|
||
{
|
||
}
|
||
|
||
bool bInited;
|
||
bool bEnabled;
|
||
|
||
std::map<std::string, Entry> entries; // tag -> 次数/耗时
|
||
|
||
unsigned long long nRegionTexts; // 一共恢复过多少份 SAT 文本
|
||
size_t nUniqueHashes; // 其中不同内容有多少
|
||
unsigned long long nSimulatedHits; // 若做内容缓存,能命中多少次
|
||
bool bHashTableOverflow;
|
||
|
||
std::set<unsigned long long> hashSeen;
|
||
|
||
unsigned long long nTempFiles; // 临时文件写入次数
|
||
unsigned long long nTempFileBytes; // 临时文件累计字节
|
||
|
||
unsigned long long nWriteBacks; // Region -> SAT 文本 次数
|
||
};
|
||
|
||
// 故意用函数内 static:AGENTS.md 禁止文件级 static。
|
||
// CoreLibrary 是静态库且只被 CoreCommand 链接,
|
||
// 所以这里的实例在 CoreCommand.arx 里是唯一的一份。
|
||
Stats& GetStats()
|
||
{
|
||
static Stats s_stats;
|
||
if (!s_stats.bInited)
|
||
{
|
||
s_stats.bInited = true;
|
||
#ifndef NDEBUG
|
||
const char* psz = ::getenv("AEC_PERF_COUNTER");
|
||
s_stats.bEnabled = (psz != NULL && psz[0] != '\0' && psz[0] != '0');
|
||
#else
|
||
s_stats.bEnabled = false;
|
||
#endif
|
||
}
|
||
return s_stats;
|
||
}
|
||
|
||
long long NowTicks()
|
||
{
|
||
LARGE_INTEGER li;
|
||
::QueryPerformanceCounter(&li);
|
||
return (long long)li.QuadPart;
|
||
}
|
||
|
||
unsigned long long TicksToUs(long long nTicks)
|
||
{
|
||
static long long nFreq = 0;
|
||
if (nFreq == 0)
|
||
{
|
||
LARGE_INTEGER li;
|
||
if (!::QueryPerformanceFrequency(&li) || li.QuadPart == 0)
|
||
nFreq = 1;
|
||
else
|
||
nFreq = (long long)li.QuadPart;
|
||
}
|
||
if (nTicks <= 0)
|
||
return 0;
|
||
return (unsigned long long)((nTicks * 1000000LL) / nFreq);
|
||
}
|
||
|
||
// FNV-1a 64
|
||
unsigned long long Hash64(const char* pszTxt, size_t nLen)
|
||
{
|
||
unsigned long long h = 1469598103934665603ULL;
|
||
for (size_t i = 0; i < nLen; ++i)
|
||
{
|
||
h ^= (unsigned char)pszTxt[i];
|
||
h *= 1099511628211ULL;
|
||
}
|
||
return h;
|
||
}
|
||
|
||
Entry& GetEntry(const char* pszTag)
|
||
{
|
||
Stats& s = GetStats();
|
||
std::string key(pszTag ? pszTag : "?");
|
||
return s.entries[key];
|
||
}
|
||
|
||
std::string TempReportPath()
|
||
{
|
||
char szTmp[MAX_PATH + 1];
|
||
::memset(szTmp, 0, sizeof(szTmp));
|
||
DWORD n = ::GetTempPathA(MAX_PATH, szTmp);
|
||
if (n == 0 || n >= MAX_PATH)
|
||
return std::string("aec_section_perf.txt");
|
||
std::string s(szTmp);
|
||
s += "aec_section_perf.txt";
|
||
return s;
|
||
}
|
||
}
|
||
|
||
namespace SectionPerf
|
||
{
|
||
bool Enabled()
|
||
{
|
||
return GetStats().bEnabled;
|
||
}
|
||
|
||
Scope::Scope(const char* pszTag)
|
||
: m_pszTag(pszTag), m_nStart(0), m_bActive(false)
|
||
{
|
||
if (!GetStats().bEnabled)
|
||
return;
|
||
m_bActive = true;
|
||
m_nStart = NowTicks();
|
||
}
|
||
|
||
Scope::~Scope()
|
||
{
|
||
if (!m_bActive)
|
||
return;
|
||
|
||
const unsigned long long nUs = TicksToUs(NowTicks() - m_nStart);
|
||
Entry& e = GetEntry(m_pszTag);
|
||
++e.nCount;
|
||
e.nTotalUs += nUs;
|
||
if (nUs > e.nMaxUs)
|
||
e.nMaxUs = nUs;
|
||
}
|
||
|
||
void NoteRegionText(const char* pszTxt, size_t nLen)
|
||
{
|
||
Stats& s = GetStats();
|
||
if (!s.bEnabled)
|
||
return;
|
||
if (pszTxt == NULL || nLen == 0)
|
||
return;
|
||
|
||
++s.nRegionTexts;
|
||
|
||
const unsigned long long h = Hash64(pszTxt, nLen);
|
||
if (s.hashSeen.find(h) != s.hashSeen.end())
|
||
{
|
||
++s.nSimulatedHits;
|
||
return;
|
||
}
|
||
if (s.hashSeen.size() < kMaxUniqueHash)
|
||
{
|
||
s.hashSeen.insert(h);
|
||
++s.nUniqueHashes;
|
||
}
|
||
else
|
||
{
|
||
s.bHashTableOverflow = true;
|
||
}
|
||
}
|
||
|
||
void NoteTempFile(size_t nBytes)
|
||
{
|
||
Stats& s = GetStats();
|
||
if (!s.bEnabled)
|
||
return;
|
||
++s.nTempFiles;
|
||
s.nTempFileBytes += (unsigned long long)nBytes;
|
||
}
|
||
|
||
void NoteWriteBack(const char* pszTag)
|
||
{
|
||
Stats& s = GetStats();
|
||
if (!s.bEnabled)
|
||
return;
|
||
++s.nWriteBacks;
|
||
(void)pszTag;
|
||
}
|
||
|
||
void Dump()
|
||
{
|
||
Stats& s = GetStats();
|
||
if (!s.bEnabled)
|
||
{
|
||
acutPrintf(_T("\n[SectionPerf] not enabled: set AEC_PERF_COUNTER=1 before starting CAD (Debug build only)."));
|
||
return;
|
||
}
|
||
|
||
// 报告正文全 ASCII:控制台用 %hs 直出,落文件也直接可读
|
||
std::string out;
|
||
char szLine[1024];
|
||
|
||
out += "\n================ SectionPerf (Phase 0) ================\n";
|
||
|
||
sprintf_s(szLine, sizeof(szLine), "RegionTexts=%llu UniqueHashes=%llu SimulatedHits=%llu HitRate=%.1f%%\n",
|
||
s.nRegionTexts, (unsigned long long)s.nUniqueHashes, s.nSimulatedHits,
|
||
s.nRegionTexts ? (100.0 * (double)s.nSimulatedHits / (double)s.nRegionTexts) : 0.0);
|
||
out += szLine;
|
||
if (s.bHashTableOverflow)
|
||
out += " (hash table capped: UniqueHashes is under-counted, HitRate over-estimated)\n";
|
||
|
||
sprintf_s(szLine, sizeof(szLine), "TempFiles=%llu TempBytes=%llu WriteBacks=%llu\n",
|
||
s.nTempFiles, s.nTempFileBytes, s.nWriteBacks);
|
||
out += szLine;
|
||
|
||
out += "---- timing (by tag) ----\n";
|
||
out += " count total_ms avg_us max_us tag\n";
|
||
for (std::map<std::string, Entry>::const_iterator it = s.entries.begin();
|
||
it != s.entries.end(); ++it)
|
||
{
|
||
const Entry& e = it->second;
|
||
const double avgUs = e.nCount ? ((double)e.nTotalUs / (double)e.nCount) : 0.0;
|
||
sprintf_s(szLine, sizeof(szLine), " %-9llu %10.1f %10.1f %10llu %s\n",
|
||
e.nCount, (double)e.nTotalUs / 1000.0, avgUs, e.nMaxUs, it->first.c_str());
|
||
out += szLine;
|
||
}
|
||
out += "=======================================================\n";
|
||
|
||
// 逐行输出:%hs 把窄字符串塞进 TCHAR 格式串,不涉及宽字符转换
|
||
const char* psz = out.c_str();
|
||
while (*psz != '\0')
|
||
{
|
||
const char* pszEnd = strchr(psz, '\n');
|
||
const size_t nSeg = (pszEnd != NULL) ? (size_t)(pszEnd - psz) : strlen(psz);
|
||
if (nSeg > 0 && nSeg < sizeof(szLine))
|
||
{
|
||
memcpy(szLine, psz, nSeg);
|
||
szLine[nSeg] = '\0';
|
||
acutPrintf(_T("\n%hs"), szLine);
|
||
}
|
||
if (pszEnd == NULL)
|
||
break;
|
||
psz = pszEnd + 1;
|
||
}
|
||
|
||
// 同时落文件,方便事后拿去做分析
|
||
const std::string sPath = TempReportPath();
|
||
FILE* fp = NULL;
|
||
if (fopen_s(&fp, sPath.c_str(), "a") == 0 && fp != NULL)
|
||
{
|
||
fprintf(fp, "%s", out.c_str());
|
||
fclose(fp);
|
||
}
|
||
}
|
||
|
||
void Reset()
|
||
{
|
||
Stats& s = GetStats();
|
||
s.entries.clear();
|
||
s.hashSeen.clear();
|
||
s.nRegionTexts = 0;
|
||
s.nUniqueHashes = 0;
|
||
s.nSimulatedHits = 0;
|
||
s.bHashTableOverflow = false;
|
||
s.nTempFiles = 0;
|
||
s.nTempFileBytes = 0;
|
||
s.nWriteBacks = 0;
|
||
}
|
||
}
|