MF4 补 ##SD/##SI/VLSD + 信号枚举文本 enumText + .mat 改随机读(流式进度)
1) 修复上轮记录的两个已知问题
- MatLoader: LoadPlainObject 不再为拿 dims 调 ReadDataset(整块解码第二次),
改用 ReadDatasetInfo;普通 v7.3 大矩阵的时间/内存不再翻倍。
- MdfFile::TextBlock 注释与实现对齐:真的支持 ##MD(UTF-8 XML,无长度前缀)。
2) 信号枚举信息(以 TestData/NEW-MF4.mf4 为目标)
- 新增 SignalEnumText(DataTable.hpp):values[] + texts[] + Empty()/Find(),
命名用 enumText —— 它描述"取值代表哪个状态",不是单位。
- DataTable::columnEnumText 与 columns 同下标;SignalData::enumText 由
BuildSignalGroup 移动赋值;其它格式(CSV/.mat)保持为空。
- MF4 cc_type = 7:cc_val[i](F64,数据段 +24)↔ cc_ref[4 + i](##TX/##MD 文本),
最后一个引用是默认文本,不使用。
- 界面只在**光标取值标注**追加 " = <枚举文本>"(等号两侧各一个空格、无尖括号),
查表用采样原值(ApplyDrawTransform 之前);常规状态显示一个像素都不变。
- 实测:NEW-MF4.mf4 8 信号 / 4 条带枚举(32+5+32+5),MF4-Record.MF4 另有 2 种(12/32)。
3) MF4 支持范围
- ##SI 采样索引:只有索引 ≥2 条且全部 < 周期数才生效(逐记录有效性位图)。
实测部分工具给每个通道挂只含常量 513/4 的 ##SI,必须忽略,否则整列变 NaN。
- ##SD + VLSD(cn_type == 1):##SD 数据段是「UINT32 长度 + 字节」样本序列,
记录里放 64 位偏移;AppendSignalData 递归展开 SD/DZ/DL/HL(单通道上限 512MB),
DecodeVlsdValue 先按直接偏移、失败再按 >>1 回退,取样本第一个元素。
- CN 的 cn_data / md_unit / md_comment 下标按链接总数反推:8 链接(MDF 4.0 风格)
= 换算[4]/数据[5]/单位[6]/注释[7],9 链接(4.1)= [5]/[6]/[7]/[8]。
实测 Simulink 导出的“4.10”仍写 8 链接,不能写死 Link(cn,4/5)。
- 主通道:cn_sync_type 1/2/3(时间/角度/距离)→ 名字 t/time/timestamp/zeit/时间
(含 t[...])→ 第一个数值通道兜底;cg_flags 第 2 位(远程主通道)跳过 sync 判定。
- 记录长度优先 cg_data_bytes(CG 数据段 +24);旧实现把 U8(cgData) 当
cg_record_id_len,其实是 cg_flags 低字节(总线事件位)。
- 进度:先走 DG 链得到真实组数(不再写死 64),hasBytes = true,bytesRead 取
MdfFile::ReadAt 累计(夹到文件大小);4MB 分块读取检查取消。
- ##DZ:数据段前有 28 字节头(original_type/zip_type/param/original_size/zip_size),
旧实现整段当 zlib 流是错的;只支持纯 deflate(zip_type = 0)。
- 一个信号都没有时,错误信息附带「布局无法解析 / 缺少时间通道 / 空数据组」计数。
4) .mat:内存映射 → 随机读 + 元数据块缓存(消除 SEH 风险)
- MatHdf5::Open 改为 CreateFileW(FILE_FLAG_SEQUENTIAL_SCAN) + ReadFile;
删除 MappedBytes,改为 ReadAt / Cached(16 × 64KB LRU + 上次命中槽)/ Tag /
ReadData / ReadCString。I/O 失败只会变成 false,不再触发页访问异常。
- 数据读取:连续布局按 4MB 切片;分块布局按需 + 64KB 对齐。
固定 4MB 预读在交错存放的分块数据集上会把读盘量放大到 2.2 倍(2874MB/1293MB),
改后 1301MB。
- 关键提速:dims=[N]、chunk=[32768,1] 归一化后 rank=2、每行 1 字节,逐行算坐标
是 12.9 亿次迭代;新增「rank == 2 且 chunkDims[1] == dims[1]」快路径(整块一次
sink)。BIG_MAT.mat(1.29GB / 5 × 16,142,471):21.5s → 7.9s,私有内存 +741MB。
- 进度:Open 立刻上报「0 / 总大小」,之后每读一块回调 ReadProgress(bytesRead,total)
→ MatLoader 映射到 0.02–0.98 并单调夹紧(字节进度与逐流解码取较大值),收尾 1.0。
遮罩层百分比不再一上来就 100%;取消会中止(StreamDataset 返回 false),不留 NaN 空洞。
- 连续布局数据不足(文件被截断)现在明确失败,不再静默截短。
验证(全部离屏:无可见窗口、不注入鼠标键盘屏幕)
- 新增 .preflight/perf/loadercheck(数据层)32/32:
NEW-MF4 8 信号 / 4 条枚举 / 枚举文本逐条核对 / 9396 与 94 行;
MF4-Record 124 信号 / 3 轴 / 4 条枚举;data-sample.mat 4 组 8 信号 / 行数;
BIG_MAT.mat 5 信号 × 16,142,471 + 进度(total 已知、ratio 单调、bytes 递增、
起点 ≤0.05、终值 1.0、更新 ≥10 次)。
- 新增 .preflight/perf/enumui(真实 Application + SW_HIDE)12/12:
只显示 gVR_WinOpenReq[0],光标落在枚举采样点上 —— 标签宽度 265px;
临时清空 enumText 后 25px;差值 240px 与 " = WinOpenReqAtDrvr_Idle" 的
UiMeasureTextWidth 完全一致;恢复后宽度回到 265px。
- 回归:datacheck 69/69、matcheck 21/21、matui 15/15(loading bytes 达到总大小)、
verify 49/49。
- make release / make debug 零警告。Build/Release/TargetCheck.exe = 820,224 字节
(上轮 785,920,+34KB)。(Target.exe 正在被用户运行的程序占用,链接步骤未覆盖它。)
This commit is contained in:
1 parent
75f0fafd41
commit
181f95cc3a
10 files changed
+1328
-291
No files matched your search
+323
-70
@@ -82,9 +82,212 @@ namespace jdi
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
/** @brief 元数据缓存的块大小与槽数(16 × 64KB = 1MB,足够覆盖对象头/B-tree 热点)。 */
|
||||
constexpr std::size_t kMetaBlockBytes = 64u * 1024u;
|
||||
constexpr std::size_t kMetaBlockSlots = 16;
|
||||
|
||||
/** @brief 顺序预读窗口上限。 */
|
||||
constexpr std::size_t kReadAheadBytes = 4u * 1024u * 1024u;
|
||||
|
||||
/**
|
||||
* @brief 数据读取的最小粒度:按请求大小向上对齐读取,命中窗口时直接复用。
|
||||
*
|
||||
* ⚠️ 分块数据集的 chunk 在文件里**并非完全顺序**(多个样本流交错写入,
|
||||
* 每段连续区域约 1.8MB)。实测 `BIG_MAT.mat`:固定预读 4MB 会让每个连续段
|
||||
* 末尾浪费 2.2MB,总读盘量达到文件大小的 **2.2 倍**(2.87GB / 1.29GB)。
|
||||
* 改为「按需 + 64KB 对齐」后,读盘量回到文件大小附近。
|
||||
*/
|
||||
constexpr std::size_t kReadGranularity = 64u * 1024u;
|
||||
}
|
||||
|
||||
bool MatHdf5::ReadAt(std::uint64_t offset, void* dst, std::size_t bytes) const
|
||||
{
|
||||
if (m_fileHandle == nullptr || bytes == 0)
|
||||
{
|
||||
return bytes == 0;
|
||||
}
|
||||
if (offset > m_size || bytes > m_size - offset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const HANDLE file = static_cast<HANDLE>(m_fileHandle);
|
||||
LARGE_INTEGER position = {};
|
||||
position.QuadPart = static_cast<LONGLONG>(offset);
|
||||
if (!SetFilePointerEx(file, position, nullptr, FILE_BEGIN))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::size_t done = 0;
|
||||
while (done < bytes)
|
||||
{
|
||||
DWORD read = 0;
|
||||
const DWORD want = static_cast<DWORD>(std::min<std::size_t>(bytes - done, 1u << 30));
|
||||
if (!ReadFile(file, static_cast<std::uint8_t*>(dst) + done, want, &read, nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (read == 0)
|
||||
{
|
||||
return false; // 文件被截断
|
||||
}
|
||||
done += read;
|
||||
}
|
||||
m_bytesRead += bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::uint8_t* MatHdf5::Cached(std::uint64_t offset, std::size_t bytes) const
|
||||
{
|
||||
if (offset >= m_size || bytes > kMetaBlockBytes)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (m_cache.empty())
|
||||
{
|
||||
m_cache.resize(kMetaBlockSlots);
|
||||
}
|
||||
const std::uint64_t base = offset - (offset % kMetaBlockBytes);
|
||||
// 快路径:连续读同一块(U16/U32/U64 与 B-tree 遍历几乎都是这种模式)。
|
||||
{
|
||||
CacheSlot& slot = m_cache[m_lastSlot];
|
||||
if (slot.length > 0 && slot.base == base)
|
||||
{
|
||||
slot.stamp = ++m_stamp;
|
||||
const std::uint64_t delta = offset - slot.base;
|
||||
return (delta + bytes <= slot.length) ? (slot.bytes.data() + delta) : nullptr;
|
||||
}
|
||||
}
|
||||
CacheSlot* victim = nullptr;
|
||||
for (std::size_t index = 0; index < m_cache.size(); ++index)
|
||||
{
|
||||
CacheSlot& slot = m_cache[index];
|
||||
if (slot.length > 0 && slot.base == base)
|
||||
{
|
||||
slot.stamp = ++m_stamp;
|
||||
m_lastSlot = index;
|
||||
const std::uint64_t delta = offset - slot.base;
|
||||
return (delta + bytes <= slot.length) ? (slot.bytes.data() + delta) : nullptr;
|
||||
}
|
||||
if (slot.length == 0 || victim == nullptr || slot.stamp < victim->stamp)
|
||||
{
|
||||
victim = &slot;
|
||||
}
|
||||
}
|
||||
if (victim == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
// 未命中:整块读入(块可能超出文件末尾,按可用长度裁剪)。
|
||||
const std::size_t wanted =
|
||||
static_cast<std::size_t>(std::min<std::uint64_t>(kMetaBlockBytes, m_size - base));
|
||||
if (bytes > wanted)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
victim->bytes.resize(wanted);
|
||||
victim->base = base;
|
||||
victim->length = static_cast<std::uint32_t>(wanted);
|
||||
victim->stamp = ++m_stamp;
|
||||
m_lastSlot = static_cast<std::size_t>(victim - m_cache.data());
|
||||
if (!ReadAt(base, victim->bytes.data(), wanted))
|
||||
{
|
||||
victim->length = 0;
|
||||
return nullptr;
|
||||
}
|
||||
return victim->bytes.data() + (offset - base);
|
||||
}
|
||||
|
||||
bool MatHdf5::Tag(std::uint64_t offset, const char* id) const
|
||||
{
|
||||
const std::uint8_t* bytes = Cached(offset, 4);
|
||||
if (bytes == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return std::memcmp(bytes, id, 4) == 0;
|
||||
}
|
||||
|
||||
void MatHdf5::ReadCString(std::uint64_t offset, std::string& out) const
|
||||
{
|
||||
out.clear();
|
||||
std::uint64_t position = offset;
|
||||
while (position < m_size && out.size() < 4096)
|
||||
{
|
||||
const std::uint8_t* bytes = Cached(position, 1);
|
||||
if (bytes == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 只消费本缓存块内剩余的字节,保证下一次循环拿到的新块仍然有效。
|
||||
const std::uint64_t blockBase = position - (position % kMetaBlockBytes);
|
||||
const std::size_t limit =
|
||||
static_cast<std::size_t>(std::min<std::uint64_t>(kMetaBlockBytes - (position - blockBase),
|
||||
m_size - position));
|
||||
std::size_t used = 0;
|
||||
while (used < limit)
|
||||
{
|
||||
if (bytes[used] == '\0')
|
||||
{
|
||||
return;
|
||||
}
|
||||
out.push_back(static_cast<char>(bytes[used]));
|
||||
++used;
|
||||
}
|
||||
position += used;
|
||||
}
|
||||
}
|
||||
|
||||
void MatHdf5::DropReadAhead() const
|
||||
{
|
||||
m_readAheadValid = false;
|
||||
m_readAheadLength = 0;
|
||||
}
|
||||
|
||||
const std::uint8_t* MatHdf5::ReadData(std::uint64_t offset, std::size_t bytes) const
|
||||
{
|
||||
if (bytes > kReadAheadBytes || offset > m_size || bytes > m_size - offset)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (m_readAheadValid && offset >= m_readAheadBase &&
|
||||
(offset - m_readAheadBase) + bytes <= m_readAheadLength)
|
||||
{
|
||||
return m_readAhead.data() + (offset - m_readAheadBase);
|
||||
}
|
||||
// 按需读取 + 64KB 对齐:命中当前窗口就直接复用,否则只读「这次需要的一段」。
|
||||
// (固定 4MB 预读在交错存放的分块数据集上会浪费 2 倍以上的 I/O。)
|
||||
const std::size_t requested = std::max<std::size_t>(bytes, kReadGranularity);
|
||||
std::size_t wanted = static_cast<std::size_t>(std::min<std::uint64_t>(
|
||||
m_size - offset, std::min<std::size_t>(kReadAheadBytes,
|
||||
((requested + kReadGranularity - 1) / kReadGranularity) *
|
||||
kReadGranularity)));
|
||||
if (m_readAhead.size() < wanted)
|
||||
{
|
||||
m_readAhead.resize(wanted);
|
||||
}
|
||||
if (!ReadAt(offset, m_readAhead.data(), wanted))
|
||||
{
|
||||
DropReadAhead();
|
||||
return nullptr;
|
||||
}
|
||||
m_readAheadBase = offset;
|
||||
m_readAheadLength = static_cast<std::uint32_t>(wanted);
|
||||
m_readAheadValid = true;
|
||||
if (m_readProgress && !m_readProgress(m_bytesRead, m_size))
|
||||
{
|
||||
m_readCancelled = true;
|
||||
DropReadAhead();
|
||||
return nullptr;
|
||||
}
|
||||
return m_readAhead.data();
|
||||
}
|
||||
|
||||
std::uint8_t MatHdf5::U8(std::uint64_t offset) const
|
||||
{
|
||||
return (offset < m_data.size()) ? m_data[static_cast<std::size_t>(offset)] : 0;
|
||||
const std::uint8_t* bytes = Cached(offset, 1);
|
||||
return (bytes != nullptr) ? *bytes : 0;
|
||||
}
|
||||
|
||||
std::uint16_t MatHdf5::U16(std::uint64_t offset) const
|
||||
@@ -121,12 +324,13 @@ namespace jdi
|
||||
error.clear();
|
||||
Close();
|
||||
|
||||
// ⚠️ **内存映射而不是整文件读入**:SDI 导出的 `.mat` 可能是 GB 级,
|
||||
// “整文件读入 + 分块解码成字节数组 + 再拷成 double”会同时驻留三份内存
|
||||
//(用户 2026-10-19 报告,并在本轮改为流式填充)。
|
||||
// 映射只占虚拟地址空间:未被解析到的页不会驻留,系统也可以在内存压力下回收。
|
||||
// ⚠️ **随机读,不映射**:SDI 导出的 `.mat` 可能是 GB 级,整文件读入会同时驻留
|
||||
//「文件 + 未压缩字节 + double」三份内存;而内存映射在文件被截断 / 网络共享掉线 /
|
||||
// 移动介质拔出时会触发 SEH(页访问异常),GCC 下的 `try/catch` 捕获不到 ⇒ 进程直接崩溃。
|
||||
// 这里改用 `ReadFile` + 元数据块缓存:任何 I/O 失败都只会变成 `false`。
|
||||
const HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
|
||||
nullptr);
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
error = L"无法打开文件。";
|
||||
@@ -139,29 +343,14 @@ namespace jdi
|
||||
error = L"无法读取文件大小。";
|
||||
return false;
|
||||
}
|
||||
const HANDLE mapping = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
|
||||
if (mapping == nullptr)
|
||||
{
|
||||
CloseHandle(file);
|
||||
error = L"无法映射文件(请确认文件可读且未被独占)。";
|
||||
return false;
|
||||
}
|
||||
const void* view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
|
||||
if (view == nullptr)
|
||||
{
|
||||
CloseHandle(mapping);
|
||||
CloseHandle(file);
|
||||
error = L"无法映射文件视图。";
|
||||
return false;
|
||||
}
|
||||
m_fileHandle = file;
|
||||
m_mapHandle = mapping;
|
||||
m_data.pointer = static_cast<const std::uint8_t*>(view);
|
||||
m_data.length = static_cast<std::size_t>(fileSize.QuadPart);
|
||||
m_size = static_cast<std::uint64_t>(fileSize.QuadPart);
|
||||
m_bytesRead = 0;
|
||||
m_readCancelled = false;
|
||||
|
||||
// 字节已全部可访问(映射完成):把“已加载 = 总大小”报给遮罩层,
|
||||
// 之后的时间主要在“解码样本流”阶段(由 MatLoader 逐流推进)。
|
||||
if (progress && !progress(m_data.size(), m_data.size()))
|
||||
// 打开阶段已知道总大小:立即上报一次,遮罩层可以显示「0 / 总大小」,
|
||||
// 之后的真实进度由 `ReadAt` 累计的已读字节数驱动。
|
||||
if (progress && !progress(0, m_size))
|
||||
{
|
||||
Close();
|
||||
error.clear(); // 用户取消:不显示错误
|
||||
@@ -169,17 +358,16 @@ namespace jdi
|
||||
}
|
||||
|
||||
// 超级块:签名可能出现在 0、512、1024…(MATLAB 用 512 字节 user block)。
|
||||
std::size_t signature = m_data.size();
|
||||
for (std::size_t index = 0; index + 8 <= m_data.size() && index <= 4096; index += 512)
|
||||
std::size_t signature = static_cast<std::size_t>(m_size);
|
||||
for (std::size_t index = 0; index + 8 <= m_size && index <= 4096; index += 512)
|
||||
{
|
||||
if (m_data[index] == 0x89 && m_data[index + 1] == 'H' && m_data[index + 2] == 'D' &&
|
||||
m_data[index + 3] == 'F')
|
||||
if (Tag(index, "\x89HDF"))
|
||||
{
|
||||
signature = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (signature == m_data.size())
|
||||
if (signature == m_size)
|
||||
{
|
||||
error = L"不是 HDF5(MATLAB 7.3)格式。";
|
||||
return false;
|
||||
@@ -200,7 +388,7 @@ namespace jdi
|
||||
m_base = U64(signature + 12);
|
||||
m_root = Resolve(U64(signature + 36));
|
||||
}
|
||||
if (m_root == 0 || m_root >= m_data.size())
|
||||
if (m_root == 0 || m_root >= m_size)
|
||||
{
|
||||
error = L"HDF5 超级块损坏。";
|
||||
return false;
|
||||
@@ -210,31 +398,27 @@ namespace jdi
|
||||
|
||||
void MatHdf5::Close()
|
||||
{
|
||||
// 先解除视图,再关映射与文件句柄(顺序不能反)。
|
||||
if (m_data.pointer != nullptr)
|
||||
{
|
||||
UnmapViewOfFile(m_data.pointer);
|
||||
}
|
||||
if (m_mapHandle != nullptr)
|
||||
{
|
||||
CloseHandle(static_cast<HANDLE>(m_mapHandle));
|
||||
m_mapHandle = nullptr;
|
||||
}
|
||||
DropReadAhead();
|
||||
m_cache.clear();
|
||||
m_lastSlot = 0;
|
||||
if (m_fileHandle != nullptr)
|
||||
{
|
||||
CloseHandle(static_cast<HANDLE>(m_fileHandle));
|
||||
m_fileHandle = nullptr;
|
||||
}
|
||||
m_data.clear();
|
||||
m_size = 0;
|
||||
m_bytesRead = 0;
|
||||
m_readCancelled = false;
|
||||
m_base = 0;
|
||||
m_root = 0;
|
||||
m_heaps.clear();
|
||||
m_readProgress = ReadProgress();
|
||||
}
|
||||
|
||||
void MatHdf5::ReadMessages(std::uint64_t address, std::vector<Message>& messages) const
|
||||
{
|
||||
messages.clear();
|
||||
if (address + 8 > m_data.size())
|
||||
if (address + 8 > m_size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -253,18 +437,21 @@ namespace jdi
|
||||
std::uint64_t offset = blocks[blockIndex].first;
|
||||
const std::uint64_t end =
|
||||
(blocks[blockIndex].second > 0) ? (offset + blocks[blockIndex].second) : 0;
|
||||
while (read < count && offset + 8 <= m_data.size() && (end == 0 || offset + 8 <= end))
|
||||
while (read < count && offset + 8 <= m_size && (end == 0 || offset + 8 <= end))
|
||||
{
|
||||
Message message;
|
||||
message.type = U16(offset);
|
||||
const std::uint16_t messageSize = U16(offset + 2);
|
||||
offset += 8;
|
||||
if (offset + messageSize > m_data.size())
|
||||
if (offset + messageSize > m_size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
message.data.resize(messageSize);
|
||||
if (messageSize > 0 && !ReadAt(offset, message.data.data(), messageSize))
|
||||
{
|
||||
break;
|
||||
}
|
||||
message.data.assign(m_data.begin() + static_cast<std::ptrdiff_t>(offset),
|
||||
m_data.begin() + static_cast<std::ptrdiff_t>(offset + messageSize));
|
||||
offset += (messageSize + 7u) & ~static_cast<std::uint16_t>(7u);
|
||||
++read;
|
||||
if (message.type == kMessageContinuation && message.data.size() >= 16)
|
||||
@@ -286,9 +473,9 @@ namespace jdi
|
||||
std::vector<std::pair<std::string, std::uint64_t>>& out) const
|
||||
{
|
||||
int guard = 0;
|
||||
while (node != 0 && node + 24 < m_data.size() && guard++ < 100000)
|
||||
while (node != 0 && node + 24 < m_size && guard++ < 100000)
|
||||
{
|
||||
if (std::memcmp(&m_data[static_cast<std::size_t>(node)], "TREE", 4) != 0)
|
||||
if (!Tag(node, "TREE"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -323,8 +510,7 @@ namespace jdi
|
||||
void MatHdf5::WalkSymbolNode(std::uint64_t node, std::uint64_t heapData,
|
||||
std::vector<std::pair<std::string, std::uint64_t>>& out) const
|
||||
{
|
||||
if (node == 0 || node + 8 > m_data.size() ||
|
||||
std::memcmp(&m_data[static_cast<std::size_t>(node)], "SNOD", 4) != 0 || heapData == 0)
|
||||
if (node == 0 || node + 8 > m_size || !Tag(node, "SNOD") || heapData == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -332,19 +518,20 @@ namespace jdi
|
||||
std::uint64_t offset = node + 8;
|
||||
for (std::uint16_t index = 0; index < count; ++index)
|
||||
{
|
||||
if (offset + 40 > m_data.size())
|
||||
if (offset + 40 > m_size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const std::uint64_t nameOffset = U64(offset);
|
||||
const std::uint64_t objectAddress = Resolve(U64(offset + 8));
|
||||
offset += 40;
|
||||
if (objectAddress == 0 || heapData + nameOffset >= m_data.size())
|
||||
if (objectAddress == 0 || heapData + nameOffset >= m_size)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const char* name = reinterpret_cast<const char*>(m_data.data() + heapData + nameOffset);
|
||||
out.emplace_back(std::string(name), objectAddress);
|
||||
std::string name;
|
||||
ReadCString(heapData + nameOffset, name);
|
||||
out.emplace_back(std::move(name), objectAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,8 +547,7 @@ namespace jdi
|
||||
const std::uint64_t btree = Resolve(ReadLE(message.data.data(), 8));
|
||||
const std::uint64_t heap = Resolve(ReadLE(message.data.data() + 8, 8));
|
||||
std::uint64_t heapData = 0;
|
||||
if (heap + 32 <= m_data.size() &&
|
||||
std::memcmp(&m_data[static_cast<std::size_t>(heap)], "HEAP", 4) == 0)
|
||||
if (heap + 32 <= m_size && Tag(heap, "HEAP"))
|
||||
{
|
||||
heapData = Resolve(U64(heap + 24));
|
||||
bool known = false;
|
||||
@@ -724,26 +910,45 @@ namespace jdi
|
||||
return sink(dataset.compact.data(), dataset.compact.size(), 0);
|
||||
}
|
||||
|
||||
// 连续布局:按 4MB 切片直接吐出(文件是映射的,这里只是指针运算,不产生额外缓冲)。
|
||||
// 连续布局:按 4MB 切片读出(随机读 + 复用缓冲,不产生整文件大小的额外内存)。
|
||||
if (dataset.layout == LayoutClass::Contiguous)
|
||||
{
|
||||
const std::uint64_t total = dataset.ElementCount();
|
||||
const std::uint64_t totalBytes = total * elementSize;
|
||||
const std::uint64_t available =
|
||||
std::min<std::uint64_t>(dataset.dataBytes,
|
||||
(dataset.dataAddress < m_data.size())
|
||||
? (m_data.size() - dataset.dataAddress)
|
||||
: 0);
|
||||
(dataset.dataAddress < m_size) ? (m_size - dataset.dataAddress)
|
||||
: 0);
|
||||
const std::uint64_t bytes = std::min(totalBytes, available);
|
||||
// ⚠️ 元数据声明了 N 个元素、文件里却不够:明确失败,而不是静默截短
|
||||
//(静默截短会让曲线“看起来只有一半”,很难诊断)。
|
||||
if (bytes < totalBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::size_t slice = kStreamSliceBytes - (kStreamSliceBytes % elementSize);
|
||||
if (slice == 0)
|
||||
{
|
||||
slice = elementSize;
|
||||
}
|
||||
if (m_slice.size() < slice)
|
||||
{
|
||||
m_slice.resize(slice);
|
||||
}
|
||||
for (std::uint64_t offset = 0; offset < bytes; offset += slice)
|
||||
{
|
||||
const std::size_t count = static_cast<std::size_t>(std::min<std::uint64_t>(slice, bytes - offset));
|
||||
if (!sink(m_data.data() + dataset.dataAddress + offset, count, offset / elementSize))
|
||||
const std::size_t count =
|
||||
static_cast<std::size_t>(std::min<std::uint64_t>(slice, bytes - offset));
|
||||
if (!ReadAt(dataset.dataAddress + offset, m_slice.data(), count))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (m_readProgress && !m_readProgress(m_bytesRead, m_size))
|
||||
{
|
||||
m_readCancelled = true;
|
||||
return false;
|
||||
}
|
||||
if (!sink(m_slice.data(), count, offset / elementSize))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -788,8 +993,7 @@ namespace jdi
|
||||
{
|
||||
const std::uint64_t node = stack.back();
|
||||
stack.pop_back();
|
||||
if (node == 0 || node + 24 > m_data.size() ||
|
||||
std::memcmp(&m_data[static_cast<std::size_t>(node)], "TREE", 4) != 0)
|
||||
if (node == 0 || node + 24 > m_size || !Tag(node, "TREE"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -799,7 +1003,7 @@ namespace jdi
|
||||
std::uint64_t offset = node + 8 + 8 + 8;
|
||||
for (std::uint16_t index = 0; index < static_cast<std::uint16_t>(entries + 1u); ++index)
|
||||
{
|
||||
const std::uint32_t chunkSize = (offset + 4 <= m_data.size()) ? U32(offset) : 0;
|
||||
const std::uint32_t chunkSize = (offset + 4 <= m_size) ? U32(offset) : 0;
|
||||
std::vector<std::uint64_t> chunkOffset(rank, 0);
|
||||
for (std::size_t axis = 0; axis < rank; ++axis)
|
||||
{
|
||||
@@ -817,12 +1021,38 @@ namespace jdi
|
||||
stack.push_back(child);
|
||||
continue;
|
||||
}
|
||||
if (child == 0 || chunkSize == 0 || child + chunkSize > m_data.size())
|
||||
if (child == 0 || chunkSize == 0 || child + chunkSize > m_size)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::uint8_t* source = m_data.data() + child;
|
||||
// 顺序预读:分块数据通常连续存放,逐 chunk 读会退化成大量小系统调用。
|
||||
const std::uint8_t* source = nullptr;
|
||||
if (chunkSize <= kReadAheadBytes)
|
||||
{
|
||||
source = ReadData(child, chunkSize);
|
||||
}
|
||||
if (source == nullptr)
|
||||
{
|
||||
if (m_chunk.size() < chunkSize)
|
||||
{
|
||||
m_chunk.resize(chunkSize);
|
||||
}
|
||||
if (!ReadAt(child, m_chunk.data(), chunkSize))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (m_readProgress && !m_readProgress(m_bytesRead, m_size))
|
||||
{
|
||||
m_readCancelled = true;
|
||||
return false;
|
||||
}
|
||||
source = m_chunk.data();
|
||||
}
|
||||
if (m_readCancelled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::size_t sourceSize = chunkSize;
|
||||
if (dataset.deflate && chunkSize != expected)
|
||||
{
|
||||
@@ -844,6 +1074,29 @@ namespace jdi
|
||||
// 归一化后 rank=2 ⇒「每行只有 1 字节」)。若逐行回调,就是上千万次
|
||||
// 小 memcpy + 上千万次 vector 分配,实测把解压吞吐拖到 ~11MB/s。
|
||||
// 因此这里把**目标连续的行合并成一次回调**(典型情况整块只需 1 次)。
|
||||
//
|
||||
// 快路径(rank == 2 且第二维完全对齐):整块在目标里就是一段连续区域,
|
||||
// 直接一次回调,省掉「每行算坐标 + 判断合并」的循环。
|
||||
// 实测 BIG_MAT.mat:`#data#` 为 129,139,768 字节 × 32KB chunk,每 chunk 32768 行,
|
||||
// 逐行循环共 12.9 亿次迭代(约占总加载时间的一半以上)。
|
||||
if (rank == 2 && chunkDims[1] == dims[1] && chunkDims[1] > 0)
|
||||
{
|
||||
const std::uint64_t startRow =
|
||||
std::min<std::uint64_t>(chunkOffset[0], dims[0]);
|
||||
const std::uint64_t rowCount =
|
||||
std::min<std::uint64_t>(chunkDims[0], dims[0] - startRow);
|
||||
const std::uint64_t byteCount = rowCount * rowBytes;
|
||||
if (startRow < dims[0] && byteCount <= static_cast<std::uint64_t>(sourceSize))
|
||||
{
|
||||
if (!sink(source, static_cast<std::size_t>(byteCount),
|
||||
startRow * chunkDims[1]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::size_t> coordinate(rank > 0 ? rank - 1 : 0, 0);
|
||||
std::uint64_t runElement = 0; // 合并段的起始元素下标
|
||||
std::size_t runSourceOffset = 0; // 合并段的源偏移
|
||||
|
||||
Reference in new issue
Block a user