diag: 量 X3dPart 几何存盘的真实体积与序列化成本
问题:上一轮判定"3D 缓存存盘对管道不做"时,顺带给 X3dPart(Rebuild 占开图 18~22%)估了个"体积 +10~40 MB、存盘 +1.5~4 s"——这是估算,没有实测,结论可能 把这条路误杀了。本次补上测量。 改动:AecDbX3dPart::EnsureCreated 里加一个探针(AEC_X3D_SIZEPROBE=1 才生效, Release 零开销):每个 part 重建后对它的 m_pSolid 做一次 AcDbBody::acisOut 到临时文件,记录 SAT 字节数与耗时,然后删文件;每 100 个 part 把汇总写 %TEMP%\aec_x3d_probe.txt。同时记录同一次 Rebuild 的耗时用于对比。 big1.dwg 实测(AutoCAD 2020 / R230 Debug,开图 95 s): - 有几何的 part:1200(1248 个里 48 个 Hide/空,无 solid) - SAT 总字节 3.56 MB,平均 2968 B/part,最大 36086 B - acisOut 平均 0.799 ms/part;Rebuild 平均 15.3 ms/part(本次合计 20.4 s) - 重建 / 序列化 = 19.1 倍 据此(都取保守侧): - DWG 体积增量 ≤ 3.4 MB(8.2 -> ~11.6 MB,+42%)——DWG 里存的是二进制 SAB, 通常比 SAT 文本更小; - 每次存盘多花 ≤ 0.96 s(1200 x 0.8 ms),且二进制写比转文本便宜、不落盘; - 开图预计省 15~18 s(重建 18~20 s,恢复按 acisOut 同量级估 1~2 s)。 结论修正:X3dPart 这条"几何存盘"是值得做的,之前的体积/耗时估算高估了 3~10 倍。 配套说明写进 3d-cache-dwg-verdict.md §7。
This commit is contained in:
1 parent
6d2815da05
commit
f840d733ca
1 file changed
+136
@@ -20,6 +20,138 @@ static const Adesk::UInt16 kMagenta = 6;
|
||||
static const Adesk::UInt16 kWhite = 7;
|
||||
static const Adesk::UInt16 kColorByLayer = 256;
|
||||
|
||||
// ===========================================================================
|
||||
// 试点探针(AEC_X3D_SIZEPROBE=1 才生效,Release 零开销)
|
||||
//
|
||||
// 目的:量清楚"把一个 part 的 m_pSolid 存进 DWG"到底要多大、多贵。
|
||||
// - acisOut 出来的 SAT 文本字节数:二进制 SAB 一般比它小,所以这是体积**上界**;
|
||||
// - acisOut 的耗时:二进制写只要格式化更少,所以这是"序列化一次"的耗时**上界**;
|
||||
// - 顺带对比同一次 Rebuild 的耗时,算出"恢复 vs 重建"的比值。
|
||||
// 汇总写 %TEMP%\aec_x3d_probe.txt(每 100 个 part 刷新一次)。
|
||||
// ===========================================================================
|
||||
namespace
|
||||
{
|
||||
struct X3dProbeStat
|
||||
{
|
||||
X3dProbeStat() : n(0), bytes(0), maxBytes(0), outUs(0), rebuildUs(0) {}
|
||||
long long n;
|
||||
long long bytes;
|
||||
long long maxBytes;
|
||||
long long outUs;
|
||||
long long rebuildUs;
|
||||
};
|
||||
|
||||
X3dProbeStat& ProbeStat() { static X3dProbeStat s; return s; }
|
||||
|
||||
bool ProbeEnabled()
|
||||
{
|
||||
static int s_nOn = -1;
|
||||
if (s_nOn < 0)
|
||||
{
|
||||
const char* psz = ::getenv("AEC_X3D_SIZEPROBE");
|
||||
s_nOn = (psz != NULL && psz[0] != '\0' && psz[0] != '0') ? 1 : 0;
|
||||
}
|
||||
return s_nOn == 1;
|
||||
}
|
||||
|
||||
long long ProbeQpc()
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
::QueryPerformanceCounter(&li);
|
||||
return (long long)li.QuadPart;
|
||||
}
|
||||
|
||||
long long ProbeQpcFreq()
|
||||
{
|
||||
static long long s_f = 0;
|
||||
if (s_f == 0)
|
||||
{
|
||||
LARGE_INTEGER li;
|
||||
if (!::QueryPerformanceFrequency(&li) || li.QuadPart == 0) s_f = 1;
|
||||
else s_f = (long long)li.QuadPart;
|
||||
}
|
||||
return s_f;
|
||||
}
|
||||
|
||||
void ProbeDump()
|
||||
{
|
||||
TCHAR szTmp[MAX_PATH + 1];
|
||||
::memset(szTmp, 0, sizeof(szTmp));
|
||||
if (::GetTempPath(MAX_PATH, szTmp) == 0)
|
||||
return;
|
||||
TCHAR szFile[MAX_PATH + 1];
|
||||
::_stprintf_s(szFile, MAX_PATH, _T("%saec_x3d_probe.txt"), szTmp);
|
||||
|
||||
FILE* fp = NULL;
|
||||
if (_tfopen_s(&fp, szFile, _T("w")) != 0 || fp == NULL)
|
||||
return;
|
||||
|
||||
const X3dProbeStat& s = ProbeStat();
|
||||
fprintf(fp, "---- X3dPart geometry size probe ----\n");
|
||||
fprintf(fp, "parts measured : %lld\n", s.n);
|
||||
fprintf(fp, "total SAT bytes : %lld (%.1f MB)\n", s.bytes, (double)s.bytes / 1048576.0);
|
||||
if (s.n > 0)
|
||||
{
|
||||
fprintf(fp, "avg SAT bytes/part : %lld\n", s.bytes / s.n);
|
||||
fprintf(fp, "max SAT bytes/part : %lld\n", s.maxBytes);
|
||||
fprintf(fp, "avg acisOut per part: %.3f ms\n", (double)s.outUs / (double)s.n / 1000.0);
|
||||
fprintf(fp, "avg Rebuild per part: %.3f ms\n", (double)s.rebuildUs / (double)s.n / 1000.0);
|
||||
if (s.outUs > 0)
|
||||
fprintf(fp, "rebuild / acisOut : %.1f x\n", (double)s.rebuildUs / (double)s.outUs);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
// 对一个 solid 量 SAT 字节数 + acisOut 耗时
|
||||
void ProbeOneSolid(AcDb3dSolid* pSolid, long long nRebuildUs)
|
||||
{
|
||||
if (!ProbeEnabled())
|
||||
return;
|
||||
|
||||
X3dProbeStat& s = ProbeStat();
|
||||
++s.n;
|
||||
s.rebuildUs += nRebuildUs;
|
||||
|
||||
if (pSolid != NULL)
|
||||
{
|
||||
TCHAR szTmp[MAX_PATH + 1];
|
||||
::memset(szTmp, 0, sizeof(szTmp));
|
||||
if (::GetTempPath(MAX_PATH, szTmp) != 0)
|
||||
{
|
||||
TCHAR szFile[MAX_PATH + 1];
|
||||
::_stprintf_s(szFile, MAX_PATH, _T("%saec_x3d_probe_%lu.sat"),
|
||||
szTmp, (unsigned long)::GetCurrentProcessId());
|
||||
|
||||
AcDbVoidPtrArray arSolids;
|
||||
arSolids.append(pSolid);
|
||||
|
||||
const long long t0 = ProbeQpc();
|
||||
Acad::ErrorStatus es = AcDbBody::acisOut(szFile, arSolids);
|
||||
const long long t1 = ProbeQpc();
|
||||
s.outUs += (t1 - t0) * 1000000LL / ProbeQpcFreq();
|
||||
|
||||
if (es == Acad::eOk)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA fad;
|
||||
::memset(&fad, 0, sizeof(fad));
|
||||
if (::GetFileAttributesEx(szFile, GetFileExInfoStandard, &fad))
|
||||
{
|
||||
const long long nSize =
|
||||
((long long)fad.nFileSizeHigh << 32) | (long long)fad.nFileSizeLow;
|
||||
s.bytes += nSize;
|
||||
if (nSize > s.maxBytes)
|
||||
s.maxBytes = nSize;
|
||||
}
|
||||
::DeleteFile(szFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.n % 100 == 0)
|
||||
ProbeDump();
|
||||
}
|
||||
}
|
||||
|
||||
static void FreeBuffer(int n,AcDbVoidPtrArray& arEntPtrs)
|
||||
{
|
||||
AcDbEntity* pEnt = NULL;
|
||||
@@ -960,7 +1092,11 @@ bool AecDbX3dPart::EnsureCreated() const
|
||||
SectionPerf::NoteEnsure("X3dPart", this, true);
|
||||
//试点计时:X3dPart 重建总耗时(用来看"几何存盘"这套机制的天花板在哪)
|
||||
SectionPerf::Scope perfScope("X3dPart::Rebuild");
|
||||
const bool bProbe = ProbeEnabled();
|
||||
const long long nProbeT0 = bProbe ? ProbeQpc() : 0;
|
||||
const bool bOk = const_cast<AecDbX3dPart*>(this)->Rebuild();
|
||||
if (bProbe)
|
||||
ProbeOneSolid(m_pSolid, (ProbeQpc() - nProbeT0) * 1000000LL / ProbeQpcFreq());
|
||||
SectionPerf::NoteEnsure(m_pSolid != NULL ? "X3dPart.rebuild_ok" : "X3dPart.rebuild_null", this, false);
|
||||
if (m_pSolid == NULL)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user