Files
Just-DataInspector/Utilities/Src/MemProbe.cpp
T
DeepSeek-V4.1-Flash 5de3763e72 perf(size): 移除多余的 dynamic_cast 并省掉 56KB 的 C++ 名字反修饰器
两处独立收益:

1. Toolbar::BuildBoxes 里唯一的 dynamic_cast 是**多余强转**(Button::widget 本来就是
   UiButtonWidget*),却让全项目必须开 RTTI。改为直接遍历 m_buttons(与控件树同源)。
   最小复现程序实测:39,936 B -> 115,200 B(+75,264 B)。
2. RTTI 关掉后 cp-demangle.o 仍然在链接里 —— 真凶是 libstdc++ 的 vterminate.o
   (引用 __cxa_demangle)。本文件自行定义 __gnu_cxx::__verbose_terminate_handler(),
   让它不再被拉入(本应用启动后会换成自己的 terminate 处理,行为等效)。

Makefile 新增 NO_RTTI=1(默认开启)-> CXXFLAGS += -fno-rtti。

实测:Release 890,880 -> 829,440 B(-61,440);.text 753,136 -> 703,616(-49,520);
.rdata 36,936 -> 26,616(-10,320);Debug 1,801,617 -> 1,734,578(-67,039)。
release/debug 全量重建零警告。
2026-11-04 02:48:59 +08:00

148 lines
5.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 内存预检验与"分配失败"兜底(配合 `-fno-exceptions` 构建)。
//
// 背景:本项目关闭了 C++ 异常(`.xdata`+`.pdata` 占产物体积约 9%,关闭后**实测省 27,136 B**:
// 909,824 → 882,688;再叠加 LTO 共减 120,832 B)。关闭后 `std::bad_alloc` 不再可捕获,
// 任何分配失败都会直接 terminate。因此:
// 1. 凡"按数据规模预留大块内存"的地方改用 `MemReserveIfRoom()`(预检验);
// 2. 万一仍有遗漏,由 `MemInstallTerminateHandler()` 给出可见提示而不是静默消失。
#include "MemProbe.hpp"
#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <exception>
namespace
{
/**
* @brief 「未捕获异常 / 分配失败」的可见提示(`std::terminate` 的兜底路径)。
*
* ⚠️ **为什么放在全局匿名命名空间**:下面要在 `namespace __gnu_cxx` 里覆盖
* libstdc++ 的默认终止处理器,而 `__gnu_cxx` 必须是**全局**命名空间
* (不能嵌套在本项目自己的 `jdi` 里)。
*
* ⚠️ 本来这个函数由 libstdc++ 的 `__gnu_cxx::__verbose_terminate_handler()` 提供,
* 但它所在的 `vterminate.o` 会链入 `cp-demangle.o`
* (Release map 实测 `.text` 49,600 + `.rdata` 6,720 ≈ **56 KB** 的 C++ 名字反修饰器,
* 对一个不打印栈回溯的 GUI 程序毫无用处)。
* 本文件自行定义该函数 ⇒ 归档成员 `vterminate.o` 不再被拉入 ⇒ demangler 消失
* (2026-11-04 代码审查,实测 −55 KB 量级)。
*
* ⚠️ 这里**不能**用任何会再次分配内存的机制(此刻堆可能已不可用):
* 只用静态字面量与 Win32 API。
*/
void MemTerminateFallbackImpl()
{
MessageBoxW(nullptr,
L"内存不足:本次操作需要的内存超过系统可用量。\n\n"
L"建议:减少同时显示的网格数或信号数,或关闭占用内存较大的程序后重试。",
L"Just DataInspector", MB_ICONERROR | MB_OK | MB_SETFOREGROUND);
std::_Exit(EXIT_FAILURE);
}
} // namespace
namespace jdi
{
namespace
{
/** @brief 可用内存查询的缓存有效期(毫秒)。 */
constexpr std::uint64_t kMemCacheMs = 200;
/**
* @brief 预留后必须保留的可用内存比例(1/8)。
*
* 不把可用内存吃光:Windows 在物理内存接近耗尽时会大量换页,
* 表现为"整个程序卡死"而不是"分配失败",对用户更糟。
*/
constexpr std::size_t kMemHeadroomRatio = 8;
std::atomic<std::uint64_t> g_memCachedAt{0};
std::atomic<std::size_t> g_memCachedBytes{0};
/**
* @brief 应用安装的 terminate 处理(`MemInstallTerminateHandler` 注册)。
*
* ⚠️ 与文件末尾的 `__gnu_cxx::__verbose_terminate_handler()` 覆盖共用同一条提示路径。
*/
void OnTerminate()
{
MemTerminateFallbackImpl();
}
} // namespace
std::size_t MemAvailablePhysical()
{
const std::uint64_t now = GetTickCount64();
const std::uint64_t last = g_memCachedAt.load(std::memory_order_relaxed);
if (last != 0 && (now - last) < kMemCacheMs)
{
const std::size_t cached = g_memCachedBytes.load(std::memory_order_relaxed);
if (cached != 0)
{
return cached;
}
}
MEMORYSTATUSEX status = {};
status.dwLength = sizeof(status);
std::size_t available = std::numeric_limits<std::size_t>::max();
if (GlobalMemoryStatusEx(&status) != FALSE)
{
available = static_cast<std::size_t>(status.ullAvailPhys);
}
g_memCachedBytes.store(available, std::memory_order_relaxed);
g_memCachedAt.store(now, std::memory_order_relaxed);
return available;
}
bool MemCanAllocate(std::size_t bytes)
{
if (bytes == 0)
{
return true;
}
const std::size_t available = MemAvailablePhysical();
if (available == std::numeric_limits<std::size_t>::max())
{
return true; // 查询失败:按"内存充足"处理(宁可尝试也不要误拒)
}
const std::size_t headroom = available / kMemHeadroomRatio;
return bytes <= (available - std::min(available, headroom));
}
void MemInstallTerminateHandler()
{
static bool installed = false;
if (installed)
{
return;
}
installed = true;
std::set_terminate(&OnTerminate);
}
} // namespace jdi
/**
* @brief **覆盖** libstdc++ 的默认终止处理器(省掉 56 KB 的 C++ 名字反修饰器)。
*
* `std::terminate()` 定义在 `eh_terminate.o` 里,它引用 `__gnu_cxx::__verbose_terminate_handler()`。
* 该符号原本由归档成员 `vterminate.o` 提供,而 `vterminate.o` 又引用 `__cxa_demangle`
* ⇒ 链接器被迫拉入 `cp-demangle.o`(Release map 实测 `.text` 49,600 B + `.rdata` 6,720 B)。
* 我们在自己的目标文件里提供同名定义,`eh_terminate.o` 的未定义引用就被满足,
* `vterminate.o` 与整个 demangler 都不会进入最终产物。
*
* ⚠️ 必须定义在**全局** `__gnu_cxx` 命名空间(不能嵌套在 `jdi` 内)。
* ⚠️ 应用启动后会通过 `jdi::MemInstallTerminateHandler()` 换上自己的处理函数,
* 因此这个默认实现只在「启动早期崩溃」时生效 —— 行为与 libstdc++ 的版本等效
* (libstdc++ 版会打印反修饰后的异常类型名,我们只给内存提示)。
*/
namespace __gnu_cxx
{
void __verbose_terminate_handler()
{
MemTerminateFallbackImpl();
}
} // namespace __gnu_cxx