refactor: use Box to store large config objects and add memory usage tests

- Refactored config-related structs to use Box for storing large objects (e.g., IRuntime, IProfiles, PrfItem) to reduce stack memory usage and improve performance.
- Updated related methods and assignments to handle Boxed types correctly.
- Added and improved unit tests to compare memory usage between Boxed and non-Boxed config objects, demonstrating the memory efficiency of Box.
- Test output now shows the size difference between stack-allocated and heap-allocated (Box) config objects.
This commit is contained in:
Tunglies
2025-06-06 14:49:23 +08:00
parent 564fe15df2
commit 689042df60
6 changed files with 91 additions and 44 deletions

View File

@@ -22,7 +22,7 @@ pub async fn get_profiles() -> CmdResult<IProfiles> {
.await; .await;
match profiles_result { match profiles_result {
Ok(Ok(profiles)) => Ok(profiles), Ok(Ok(profiles)) => Ok(*profiles),
Ok(Err(join_err)) => { Ok(Err(join_err)) => {
logging!(error, Type::Cmd, true, "获取配置列表任务失败: {}", join_err); logging!(error, Type::Cmd, true, "获取配置列表任务失败: {}", join_err);
Ok(IProfiles { Ok(IProfiles {
@@ -41,7 +41,7 @@ pub async fn get_profiles() -> CmdResult<IProfiles> {
match tokio::task::spawn_blocking(move || Config::profiles().latest().clone()).await { match tokio::task::spawn_blocking(move || Config::profiles().latest().clone()).await {
Ok(profiles) => { Ok(profiles) => {
logging!(info, Type::Cmd, true, "使用latest()成功获取配置"); logging!(info, Type::Cmd, true, "使用latest()成功获取配置");
Ok(profiles) Ok(*profiles)
} }
Err(_) => { Err(_) => {
logging!(error, Type::Cmd, true, "fallback获取配置也失败返回空配置"); logging!(error, Type::Cmd, true, "fallback获取配置也失败返回空配置");

View File

@@ -6,7 +6,7 @@ use crate::{config::*, feat, wrap_err};
pub fn get_verge_config() -> CmdResult<IVergeResponse> { pub fn get_verge_config() -> CmdResult<IVergeResponse> {
let verge = Config::verge(); let verge = Config::verge();
let verge_data = verge.data().clone(); let verge_data = verge.data().clone();
Ok(IVergeResponse::from(verge_data)) Ok(IVergeResponse::from(*verge_data))
} }
/// 修改Verge配置 /// 修改Verge配置

View File

@@ -15,10 +15,10 @@ pub const RUNTIME_CONFIG: &str = "clash-verge.yaml";
pub const CHECK_CONFIG: &str = "clash-verge-check.yaml"; pub const CHECK_CONFIG: &str = "clash-verge-check.yaml";
pub struct Config { pub struct Config {
clash_config: Draft<IClashTemp>, clash_config: Draft<Box<IClashTemp>>,
verge_config: Draft<IVerge>, verge_config: Draft<Box<IVerge>>,
profiles_config: Draft<IProfiles>, profiles_config: Draft<Box<IProfiles>>,
runtime_config: Draft<IRuntime>, runtime_config: Draft<Box<IRuntime>>,
} }
impl Config { impl Config {
@@ -26,26 +26,26 @@ impl Config {
static CONFIG: OnceCell<Config> = OnceCell::new(); static CONFIG: OnceCell<Config> = OnceCell::new();
CONFIG.get_or_init(|| Config { CONFIG.get_or_init(|| Config {
clash_config: Draft::from(IClashTemp::new()), clash_config: Draft::from(Box::new(IClashTemp::new())),
verge_config: Draft::from(IVerge::new()), verge_config: Draft::from(Box::new(IVerge::new())),
profiles_config: Draft::from(IProfiles::new()), profiles_config: Draft::from(Box::new(IProfiles::new())),
runtime_config: Draft::from(IRuntime::new()), runtime_config: Draft::from(Box::new(IRuntime::new())),
}) })
} }
pub fn clash() -> Draft<IClashTemp> { pub fn clash() -> Draft<Box<IClashTemp>> {
Self::global().clash_config.clone() Self::global().clash_config.clone()
} }
pub fn verge() -> Draft<IVerge> { pub fn verge() -> Draft<Box<IVerge>> {
Self::global().verge_config.clone() Self::global().verge_config.clone()
} }
pub fn profiles() -> Draft<IProfiles> { pub fn profiles() -> Draft<Box<IProfiles>> {
Self::global().profiles_config.clone() Self::global().profiles_config.clone()
} }
pub fn runtime() -> Draft<IRuntime> { pub fn runtime() -> Draft<Box<IRuntime>> {
Self::global().runtime_config.clone() Self::global().runtime_config.clone()
} }
@@ -149,11 +149,11 @@ impl Config {
pub async fn generate() -> Result<()> { pub async fn generate() -> Result<()> {
let (config, exists_keys, logs) = enhance::enhance().await; let (config, exists_keys, logs) = enhance::enhance().await;
*Config::runtime().draft() = IRuntime { *Config::runtime().draft() = Box::new(IRuntime {
config: Some(config), config: Some(config),
exists_keys, exists_keys,
chain_logs: logs, chain_logs: logs,
}; });
Ok(()) Ok(())
} }
@@ -164,3 +164,42 @@ pub enum ConfigType {
Run, Run,
Check, Check,
} }
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
fn test_prfitem_from_merge_size() {
let merge_item = PrfItem::from_merge(Some("Merge".to_string())).unwrap();
dbg!(&merge_item);
let prfitem_size = mem::size_of_val(&merge_item);
dbg!(prfitem_size);
// Boxed version
let boxed_merge_item = Box::new(merge_item);
let box_prfitem_size = mem::size_of_val(&boxed_merge_item);
dbg!(box_prfitem_size);
// The size of Box<T> is always pointer-sized (usually 8 bytes on 64-bit)
// assert_eq!(box_prfitem_size, mem::size_of::<Box<PrfItem>>());
assert!(box_prfitem_size < prfitem_size);
}
#[test]
fn test_draft_size_non_boxed() {
let draft = Draft::from(IRuntime::new());
let iruntime_size = std::mem::size_of_val(&draft);
dbg!(iruntime_size);
assert_eq!(iruntime_size, std::mem::size_of::<Draft<IRuntime>>());
}
#[test]
fn test_draft_size_boxed() {
let draft = Draft::from(Box::new(IRuntime::new()));
let box_iruntime_size = std::mem::size_of_val(&draft);
dbg!(box_iruntime_size);
assert_eq!(
box_iruntime_size,
std::mem::size_of::<Draft<Box<IRuntime>>>()
);
}
}

View File

@@ -9,13 +9,21 @@ pub struct Draft<T: Clone + ToOwned> {
macro_rules! draft_define { macro_rules! draft_define {
($id: ident) => { ($id: ident) => {
impl Draft<$id> { impl From<$id> for Draft<$id> {
fn from(data: $id) -> Self {
Draft {
inner: Arc::new(Mutex::new((data, None))),
}
}
}
impl Draft<Box<$id>> {
#[allow(unused)] #[allow(unused)]
pub fn data(&self) -> MappedMutexGuard<$id> { pub fn data(&self) -> MappedMutexGuard<Box<$id>> {
MutexGuard::map(self.inner.lock(), |guard| &mut guard.0) MutexGuard::map(self.inner.lock(), |guard| &mut guard.0)
} }
pub fn latest(&self) -> MappedMutexGuard<$id> { pub fn latest(&self) -> MappedMutexGuard<Box<$id>> {
MutexGuard::map(self.inner.lock(), |inner| { MutexGuard::map(self.inner.lock(), |inner| {
if inner.1.is_none() { if inner.1.is_none() {
&mut inner.0 &mut inner.0
@@ -25,7 +33,7 @@ macro_rules! draft_define {
}) })
} }
pub fn draft(&self) -> MappedMutexGuard<$id> { pub fn draft(&self) -> MappedMutexGuard<Box<$id>> {
MutexGuard::map(self.inner.lock(), |inner| { MutexGuard::map(self.inner.lock(), |inner| {
if inner.1.is_none() { if inner.1.is_none() {
inner.1 = Some(inner.0.clone()); inner.1 = Some(inner.0.clone());
@@ -35,7 +43,7 @@ macro_rules! draft_define {
}) })
} }
pub fn apply(&self) -> Option<$id> { pub fn apply(&self) -> Option<Box<$id>> {
let mut inner = self.inner.lock(); let mut inner = self.inner.lock();
match inner.1.take() { match inner.1.take() {
@@ -48,14 +56,14 @@ macro_rules! draft_define {
} }
} }
pub fn discard(&self) -> Option<$id> { pub fn discard(&self) -> Option<Box<$id>> {
let mut inner = self.inner.lock(); let mut inner = self.inner.lock();
inner.1.take() inner.1.take()
} }
} }
impl From<$id> for Draft<$id> { impl From<Box<$id>> for Draft<Box<$id>> {
fn from(data: $id) -> Self { fn from(data: Box<$id>) -> Self {
Draft { Draft {
inner: Arc::new(Mutex::new((data, None))), inner: Arc::new(Mutex::new((data, None))),
} }
@@ -71,12 +79,12 @@ draft_define!(IRuntime);
draft_define!(IVerge); draft_define!(IVerge);
#[test] #[test]
fn test_draft() { fn test_draft_box() {
let verge = IVerge { let verge = Box::new(IVerge {
enable_auto_launch: Some(true), enable_auto_launch: Some(true),
enable_tun_mode: Some(false), enable_tun_mode: Some(false),
..IVerge::default() ..IVerge::default()
}; });
let draft = Draft::from(verge); let draft = Draft::from(verge);
@@ -86,10 +94,11 @@ fn test_draft() {
assert_eq!(draft.draft().enable_auto_launch, Some(true)); assert_eq!(draft.draft().enable_auto_launch, Some(true));
assert_eq!(draft.draft().enable_tun_mode, Some(false)); assert_eq!(draft.draft().enable_tun_mode, Some(false));
let mut d = draft.draft(); {
d.enable_auto_launch = Some(false); let mut d = draft.draft();
d.enable_tun_mode = Some(true); d.enable_auto_launch = Some(false);
drop(d); d.enable_tun_mode = Some(true);
}
assert_eq!(draft.data().enable_auto_launch, Some(true)); assert_eq!(draft.data().enable_auto_launch, Some(true));
assert_eq!(draft.data().enable_tun_mode, Some(false)); assert_eq!(draft.data().enable_tun_mode, Some(false));
@@ -109,18 +118,17 @@ fn test_draft() {
assert_eq!(draft.draft().enable_auto_launch, Some(false)); assert_eq!(draft.draft().enable_auto_launch, Some(false));
assert_eq!(draft.draft().enable_tun_mode, Some(true)); assert_eq!(draft.draft().enable_tun_mode, Some(true));
let mut d = draft.draft(); {
d.enable_auto_launch = Some(true); let mut d = draft.draft();
drop(d); d.enable_auto_launch = Some(true);
}
assert_eq!(draft.data().enable_auto_launch, Some(false)); assert_eq!(draft.data().enable_auto_launch, Some(false));
assert_eq!(draft.draft().enable_auto_launch, Some(true)); assert_eq!(draft.draft().enable_auto_launch, Some(true));
assert!(draft.discard().is_some()); assert!(draft.discard().is_some());
assert_eq!(draft.data().enable_auto_launch, Some(false)); assert_eq!(draft.data().enable_auto_launch, Some(false));
assert!(draft.discard().is_none()); assert!(draft.discard().is_none());
assert_eq!(draft.draft().enable_auto_launch, Some(false)); assert_eq!(draft.draft().enable_auto_launch, Some(false));

View File

@@ -140,11 +140,11 @@ impl CoreManager {
/// 使用默认配置 /// 使用默认配置
pub async fn use_default_config(&self, msg_type: &str, msg_content: &str) -> Result<()> { pub async fn use_default_config(&self, msg_type: &str, msg_content: &str) -> Result<()> {
let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG); let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG);
*Config::runtime().draft() = IRuntime { *Config::runtime().draft() = Box::new(IRuntime {
config: Some(Config::clash().latest().0.clone()), config: Some(Config::clash().latest().0.clone()),
exists_keys: vec![], exists_keys: vec![],
chain_logs: Default::default(), chain_logs: Default::default(),
}; });
help::save_yaml( help::save_yaml(
&runtime_path, &runtime_path,
&Config::clash().latest().0, &Config::clash().latest().0,

View File

@@ -104,10 +104,10 @@ fn test_script() {
let (config, results) = use_script(script.into(), config, "".to_string()).unwrap(); let (config, results) = use_script(script.into(), config, "".to_string()).unwrap();
let _ = serde_yaml::to_string(&config).unwrap(); let _ = serde_yaml::to_string(&config).unwrap();
let origin_size = std::mem::size_of_val(&config); let yaml_config_size = std::mem::size_of_val(&config);
dbg!(origin_size); dbg!(yaml_config_size);
let box_size = std::mem::size_of_val(&Box::new(config)); let box_yaml_config_size = std::mem::size_of_val(&Box::new(config));
dbg!(box_size); dbg!(box_yaml_config_size);
dbg!(results); dbg!(results);
assert!(origin_size > box_size); assert!(box_yaml_config_size < yaml_config_size);
} }