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:
company-desktop committed 2026-10-29 23:00:03 +08:00
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)
{