#include "stdafx.h" #include "SectionPerfCounter.h" #include #include #include #include #include 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 entries; // tag -> 次数/耗时 unsigned long long nRegionTexts; // 一共恢复过多少份 SAT 文本 size_t nUniqueHashes; // 其中不同内容有多少 unsigned long long nSimulatedHits; // 若做内容缓存,能命中多少次 bool bHashTableOverflow; std::set 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::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; } }