#include "stdafx.h" #include "SectionRegionCache.h" #include "AecDbObjXDataMap.h" #include "SectionPerfCounter.h" #include #include #include #include #include namespace { // 每个通道的条目上限。实测大图纸唯一 SAT 文本约 2600 条,2048 会把热条目挤掉, // 所以默认给 4096;可用 AEC_SECTION_CACHE_MAX 覆盖(内存吃紧时调小)。 const size_t kDefaultMaxEntries = 4096; size_t GetMaxEntries() { static size_t s_nMax = 0; if (s_nMax == 0) { const char* psz = ::getenv("AEC_SECTION_CACHE_MAX"); s_nMax = (psz != NULL) ? (size_t)atoi(psz) : 0; if (s_nMax < 16) s_nMax = kDefaultMaxEntries; } return s_nMax; } static const char* kChannelName[] = { "text", "attr", "param" }; struct ChannelData { ChannelData() : nHits(0), nMisses(0), nEvicted(0) {} std::map map; std::list fifo; unsigned long long nHits; unsigned long long nMisses; unsigned long long nEvicted; void ReleaseAll() { for (std::map::iterator it = map.begin(); it != map.end(); ++it) { AcDbRegion* p = it->second; if (p != NULL) delete p; } map.clear(); fifo.clear(); nEvicted = 0; } }; struct State { State() : bInited(false), bEnabled(true) {} bool bInited; bool bEnabled; ChannelData channels[SectionRegionCache::kChannelCount]; CRITICAL_SECTION cs; }; // 函数内 static:CoreLibrary 是静态库且只被 CoreCommand 链接, // 所以 CoreCommand.arx 内只有这一份缓存。 // 注意:不在析构里 delete 母体(DLL 卸载时 modeler 可能已先关, // 析构顺序不安全);由 ClearAll() 在 kUnloadAppMsg 里显式释放。 State& GetState() { static State s_state; if (!s_state.bInited) { ::InitializeCriticalSection(&s_state.cs); const char* psz = ::getenv("AEC_SECTION_CACHE"); s_state.bEnabled = !(psz != NULL && psz[0] == '0'); s_state.bInited = true; } return s_state; } struct AutoLock { explicit AutoLock(CRITICAL_SECTION* pcs) : m_pcs(pcs) { ::EnterCriticalSection(m_pcs); } ~AutoLock() { ::LeaveCriticalSection(m_pcs); } CRITICAL_SECTION* m_pcs; }; unsigned long long HashAdd(unsigned long long h, const void* pData, size_t nLen) { if (pData == NULL || nLen == 0) return h; const unsigned char* p = (const unsigned char*)pData; for (size_t i = 0; i < nLen; ++i) { h ^= (unsigned long long)p[i]; h *= 1099511628211ULL; } return h; } } namespace { // 诊断用:AEC_SAT_SAMPLE=n 时把前 n 个"未命中的 SAT 文本"导出,便于比对差异来源 void DumpSample(const void* pData, size_t nLen) { static int s_nRemain = -1; if (s_nRemain < 0) { const char* psz = ::getenv("AEC_SAT_SAMPLE"); s_nRemain = (psz != NULL) ? atoi(psz) : 0; } if (s_nRemain <= 0) return; --s_nRemain; char szTmp[MAX_PATH + 1]; ::memset(szTmp, 0, sizeof(szTmp)); if (::GetTempPathA(MAX_PATH, szTmp) == 0) return; std::string sPath(szTmp); sPath += "aec_sat_samples.txt"; FILE* fp = NULL; if (fopen_s(&fp, sPath.c_str(), "ab") == 0 && fp != NULL) { fprintf(fp, "===== SAMPLE len=%llu =====\n", (unsigned long long)nLen); fwrite(pData, 1, nLen, fp); fprintf(fp, "\n===== END =====\n"); fclose(fp); } } } namespace SectionRegionCache { bool Enabled() { return GetState().bEnabled; } unsigned long long HashBytes(const void* pData, size_t nLen) { return HashAdd(1469598103934665603ULL, pData, nLen); } unsigned long long HashSatText(const void* pData, size_t nLen) { if (pData == NULL || nLen == 0) return 1469598103934665603ULL; const char* p = (const char*)pData; const char* pEnd = p + nLen; // 找第 2 行(SAT 的 16 号记录:Autodesk 版本 + 存盘时间戳) const char* pNL1 = (const char*)memchr(p, '\n', nLen); if (pNL1 != NULL && pNL1 + 1 < pEnd) { const char* pLine2 = pNL1 + 1; const char* pNL2 = (const char*)memchr(pLine2, '\n', (size_t)(pEnd - pLine2)); if (pNL2 != NULL) { // 只在第 2 行确实是 Autodesk 头时才跳过,避免误伤别的格式 bool bHeader = false; for (const char* q = pLine2; q + 9 <= pNL2; ++q) { if (_strnicmp(q, "Autodesk", 8) == 0) { bHeader = true; break; } } if (bHeader) { unsigned long long h = HashBytes(p, (size_t)(pNL1 - p)); return HashAdd(h, pNL2 + 1, (size_t)(pEnd - (pNL2 + 1))); } } } return HashBytes(pData, nLen); } unsigned long long BuildSpecAttrHash(const AecDbObjXDataMap& map, const TCHAR* pszClass) { unsigned long long h = 1469598103934665603ULL; // 类名参与:不同实体类即使数据相同也绝不能共用截面 CStringA sCls(pszClass != NULL ? pszClass : _T("?")); h = HashAdd(h, (LPCSTR)sCls, strlen((LPCSTR)sCls)); // std::map 按 key 排序,遍历顺序稳定 for (AecDbObjXDataMap::It it = const_cast(map).Begin(); it != const_cast(map).End(); ++it) { const CString& sKey = it->first; const AecXData& xd = it->second; // 编号每根管子唯一,必须排除 if (sKey.CompareNoCase(_T("No.")) == 0) continue; h = HashAdd(h, sKey.GetString(), (size_t)sKey.GetLength() * sizeof(TCHAR)); h = HashAdd(h, &xd.nVt, sizeof(xd.nVt)); switch (xd.nVt) { case AecXData::kTypeShort: h = HashAdd(h, &xd.iVal, sizeof(xd.iVal)); break; case AecXData::kTypeInt: case AecXData::kTypeInt64: h = HashAdd(h, &xd.nVal, sizeof(xd.nVal)); break; case AecXData::kTypeDouble: h = HashAdd(h, &xd.dVal[0], sizeof(double)); break; case AecXData::kTypeString: if (xd.pszVal != NULL) h = HashAdd(h, xd.pszVal, _tcslen(xd.pszVal) * sizeof(TCHAR)); break; default: // Id / 点 / 向量 / 矩阵 / 二进制:与截面无关或随实体变化,跳过 break; } } return h; } AcDbRegion* Acquire(Channel ch, unsigned long long nKey, const void* pSampleData, size_t nSampleLen) { State& s = GetState(); if (!s.bEnabled || ch < 0 || ch >= kChannelCount) return NULL; AutoLock lock(&s.cs); ChannelData& cd = s.channels[ch]; std::map::iterator it = cd.map.find(nKey); if (it == cd.map.end() || it->second == NULL) { ++cd.nMisses; if (ch == SectionRegionCache::kText && pSampleData != NULL) DumpSample(pSampleData, nSampleLen); return NULL; } ++cd.nHits; SectionPerf::Scope hitScope("cache.hit.clone"); return (AcDbRegion*)it->second->clone(); } AcDbRegion* Put(Channel ch, unsigned long long nKey, AcDbRegion* pMaster) { State& s = GetState(); if (pMaster == NULL) return NULL; if (!s.bEnabled || ch < 0 || ch >= kChannelCount) return pMaster; // 关闭时原样返回,调用方照旧拥有 AcDbRegion* pForCaller = NULL; { SectionPerf::Scope putScope("cache.miss.clone"); pForCaller = (AcDbRegion*)pMaster->clone(); } AutoLock lock(&s.cs); ChannelData& cd = s.channels[ch]; std::map::iterator it = cd.map.find(nKey); if (it != cd.map.end()) { // 已有同 key(并发或重复入库):丢弃新的母体 delete pMaster; return pForCaller; } // 超出上限先淘汰最旧的 while (cd.map.size() >= GetMaxEntries() && !cd.fifo.empty()) { const unsigned long long nOld = cd.fifo.front(); cd.fifo.pop_front(); std::map::iterator itOld = cd.map.find(nOld); if (itOld != cd.map.end()) { AcDbRegion* pOld = itOld->second; cd.map.erase(itOld); delete pOld; ++cd.nEvicted; } } cd.map[nKey] = pMaster; cd.fifo.push_back(nKey); return pForCaller; } void Dump() { State& s = GetState(); std::string out; char szLine[512]; out += "\n---- section region cache ----\n"; out += " channel entries hits misses hit_rate evicted\n"; for (int i = 0; i < (int)kChannelCount; ++i) { const ChannelData& cd = s.channels[i]; const unsigned long long nTotal = cd.nHits + cd.nMisses; const double rate = nTotal ? (100.0 * (double)cd.nHits / (double)nTotal) : 0.0; sprintf_s(szLine, sizeof(szLine), " %-9s %-9llu %-7llu %-7llu %7.1f%% %llu\n", kChannelName[i], (unsigned long long)cd.map.size(), cd.nHits, cd.nMisses, rate, cd.nEvicted); out += szLine; } 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; } // 同时写进与 SectionPerf 相同的报告文件,便于脚本回收 SectionPerf::AppendReport(out.c_str()); } void ClearAll() { State& s = GetState(); AutoLock lock(&s.cs); for (int i = 0; i < (int)kChannelCount; ++i) s.channels[i].ReleaseAll(); } }