344 lines
9.0 KiB
C++
344 lines
9.0 KiB
C++
#include "stdafx.h"
|
||||
|
|
#include "SectionRegionCache.h"
|
|||
|
|
#include "AecDbObjXDataMap.h"
|
|||
|
|
#include "SectionPerfCounter.h"
|
|||
|
|
|
|||
|
|
#include <windows.h>
|
|||
|
|
#include <stdlib.h>
|
|||
|
|
#include <string>
|
|||
|
|
#include <map>
|
|||
|
|
#include <list>
|
|||
|
|
|
|||
|
|
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<unsigned long long, AcDbRegion*> map;
|
|||
|
|
std::list<unsigned long long> fifo;
|
|||
|
|
unsigned long long nHits;
|
|||
|
|
unsigned long long nMisses;
|
|||
|
|
unsigned long long nEvicted;
|
|||
|
|
|
|||
|
|
void ReleaseAll()
|
|||
|
|
{
|
|||
|
|
for (std::map<unsigned long long, AcDbRegion*>::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<AecDbObjXDataMap&>(map).Begin();
|
|||
|
|
it != const_cast<AecDbObjXDataMap&>(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<unsigned long long, AcDbRegion*>::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<unsigned long long, AcDbRegion*>::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<unsigned long long, AcDbRegion*>::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();
|
|||
|
|
}
|
|||
|
|
}
|