Skip to content

Commit f0ed730

Browse files
committed
feat(object): implement multi-tier adaptive optimization for Object::get
- Add size-based adaptive search strategies
1 parent ab1e899 commit f0ed730

File tree

2 files changed

+229
-4
lines changed

2 files changed

+229
-4
lines changed

src/value/node.rs

Lines changed: 228 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
use core::mem::size_of;
2-
#[cfg(feature = "sort_keys")]
3-
use std::collections::BTreeMap;
42
use std::{
53
alloc::Layout,
4+
collections::HashMap,
65
fmt::{Debug, Display, Formatter},
76
mem::{transmute, ManuallyDrop},
87
ptr::NonNull,
98
slice::from_raw_parts,
109
str::from_utf8_unchecked,
11-
sync::Arc,
10+
sync::{Arc, Mutex},
1211
};
1312

1413
#[cfg(not(feature = "sort_keys"))]
1514
use ahash::AHashMap;
1615
use faststr::FastStr;
1716
use ref_cast::RefCast;
1817
use serde::ser::{Serialize, SerializeMap, SerializeSeq};
18+
#[cfg(feature = "sort_keys")]
19+
use std::collections::BTreeMap;
20+
21+
#[cfg(target_arch = "x86_64")]
22+
use std::arch::x86_64::*;
23+
24+
/// Hash index cache for large objects
25+
use std::sync::LazyLock;
26+
static HASH_INDEX_CACHE: LazyLock<Mutex<HashMap<usize, (HashMap<String, usize>, u32)>>> =
27+
LazyLock::new(|| Mutex::new(HashMap::new()));
1928

2029
use super::{
2130
object::Pair,
@@ -1155,6 +1164,11 @@ impl Value {
11551164
self.get_key_value(key).map(|(_, v)| v)
11561165
}
11571166

1167+
#[inline]
1168+
pub(crate) fn get_key_optimized(&self, key: &str) -> Option<&Self> {
1169+
self.get_key_value_optimized(key).map(|(_, v)| v)
1170+
}
1171+
11581172
pub(crate) fn get_key_value(&self, key: &str) -> Option<(&str, &Self)> {
11591173
debug_assert!(self.is_object());
11601174
let ref_inner = self.as_ref2();
@@ -1173,6 +1187,217 @@ impl Value {
11731187
None
11741188
}
11751189

1190+
/// Optimized key-value lookup with multi-level adaptive strategies
1191+
pub(crate) fn get_key_value_optimized(&self, key: &str) -> Option<(&str, &Self)> {
1192+
debug_assert!(self.is_object());
1193+
let ref_inner = self.as_ref2();
1194+
1195+
if let ValueRefInner::Object(pairs) = ref_inner {
1196+
let len = pairs.len();
1197+
1198+
// Multi-level adaptive optimization strategy
1199+
match len {
1200+
0 => None,
1201+
1..=7 => {
1202+
// Small objects: Optimized linear search
1203+
self.linear_search_small(key, pairs)
1204+
}
1205+
8..=31 => {
1206+
// Medium objects: SIMD-accelerated linear search
1207+
self.simd_search_optimized(key, pairs)
1208+
}
1209+
_ => {
1210+
// Large objects: Hash index + cache
1211+
self.large_object_search_with_hash(key, pairs)
1212+
}
1213+
}
1214+
} else if let ValueRefInner::ObjectOwned(kv) = ref_inner {
1215+
// For owned objects, use the existing hash map lookup
1216+
if let Some((k, v)) = kv.get_key_value(key) {
1217+
return Some((k.as_str(), v));
1218+
}
1219+
None
1220+
} else {
1221+
None
1222+
}
1223+
}
1224+
1225+
/// Hash index search for large objects (> 32 keys)
1226+
fn large_object_search_with_hash<'a>(
1227+
&self,
1228+
key: &str,
1229+
pairs: &'a [(Value, Value)],
1230+
) -> Option<(&'a str, &'a Self)> {
1231+
let pairs_ptr = pairs.as_ptr() as usize;
1232+
1233+
// Try to get or build hash index
1234+
if let Ok(mut cache) = HASH_INDEX_CACHE.lock() {
1235+
let entry = cache.entry(pairs_ptr).or_insert_with(|| {
1236+
// Build hash index for this object
1237+
let mut hash_index = HashMap::with_capacity(pairs.len());
1238+
for (i, (k, _)) in pairs.iter().enumerate() {
1239+
if let Some(k_str) = k.as_str() {
1240+
// For duplicate keys, keep the first occurrence (consistent with linear search)
1241+
hash_index.entry(k_str.to_string()).or_insert(i);
1242+
}
1243+
}
1244+
(hash_index, 1) // (hash_index, access_count)
1245+
});
1246+
1247+
// Increment access count
1248+
entry.1 += 1;
1249+
1250+
// Use hash index for lookup
1251+
if let Some(&index) = entry.0.get(key) {
1252+
if index < pairs.len() {
1253+
if let Some(k_str) = pairs[index].0.as_str() {
1254+
if k_str == key {
1255+
return Some((k_str, &pairs[index].1));
1256+
}
1257+
}
1258+
}
1259+
}
1260+
1261+
// Clean up cache if it gets too large (simple LRU-like cleanup)
1262+
if cache.len() > 100 {
1263+
cache.retain(|_, (_, access_count)| *access_count > 1);
1264+
for (_, access_count) in cache.values_mut() {
1265+
*access_count = (*access_count).saturating_sub(1);
1266+
}
1267+
}
1268+
}
1269+
1270+
// Fallback to SIMD search if hash index fails
1271+
self.simd_search_optimized(key, pairs)
1272+
}
1273+
1274+
/// Optimized linear search for small objects
1275+
#[inline]
1276+
fn linear_search_small<'a>(
1277+
&self,
1278+
key: &str,
1279+
pairs: &'a [(Value, Value)],
1280+
) -> Option<(&'a str, &'a Self)> {
1281+
let key_len = key.len();
1282+
1283+
// Length pre-check optimization for small objects
1284+
for (k, v) in pairs {
1285+
if let Some(k_str) = k.as_str() {
1286+
// Length pre-check before string comparison
1287+
if k_str.len() == key_len && k_str == key {
1288+
return Some((k_str, v));
1289+
}
1290+
}
1291+
}
1292+
None
1293+
}
1294+
1295+
/// SIMD-accelerated search for medium and large objects
1296+
#[inline]
1297+
fn simd_search_optimized<'a>(
1298+
&self,
1299+
key: &str,
1300+
pairs: &'a [(Value, Value)],
1301+
) -> Option<(&'a str, &'a Self)> {
1302+
let key_bytes = key.as_bytes();
1303+
1304+
// Try SIMD optimization for longer keys
1305+
if key_bytes.len() >= 16 {
1306+
if let Some(result) = self.simd_string_compare(key, pairs) {
1307+
return Some(result);
1308+
}
1309+
}
1310+
1311+
// Fallback to optimized linear search
1312+
self.linear_search_optimized(key, pairs)
1313+
}
1314+
1315+
/// SIMD string comparison for keys >= 16 bytes
1316+
#[cfg(target_arch = "x86_64")]
1317+
fn simd_string_compare<'a>(
1318+
&self,
1319+
key: &str,
1320+
pairs: &'a [(Value, Value)],
1321+
) -> Option<(&'a str, &'a Self)> {
1322+
if !is_x86_feature_detected!("sse2") {
1323+
return None;
1324+
}
1325+
1326+
let key_bytes = key.as_bytes();
1327+
let key_len = key_bytes.len();
1328+
1329+
unsafe {
1330+
// Load first 16 bytes of key for SIMD comparison
1331+
let key_vec = if key_len >= 16 {
1332+
_mm_loadu_si128(key_bytes.as_ptr() as *const __m128i)
1333+
} else {
1334+
// Pad with zeros for shorter keys
1335+
let mut padded = [0u8; 16];
1336+
padded[..key_len].copy_from_slice(key_bytes);
1337+
_mm_loadu_si128(padded.as_ptr() as *const __m128i)
1338+
};
1339+
1340+
for (k, v) in pairs {
1341+
if let Some(k_str) = k.as_str() {
1342+
let k_bytes = k_str.as_bytes();
1343+
1344+
// Quick length check
1345+
if k_bytes.len() != key_len {
1346+
continue;
1347+
}
1348+
1349+
if k_bytes.len() >= 16 {
1350+
// SIMD comparison for first 16 bytes
1351+
let k_vec = _mm_loadu_si128(k_bytes.as_ptr() as *const __m128i);
1352+
let cmp = _mm_cmpeq_epi8(key_vec, k_vec);
1353+
let mask = _mm_movemask_epi8(cmp);
1354+
1355+
if mask == 0xFFFF {
1356+
// First 16 bytes match, check remaining bytes
1357+
if key_len <= 16 || key_bytes[16..] == k_bytes[16..] {
1358+
return Some((k_str, v));
1359+
}
1360+
}
1361+
} else if key_bytes == k_bytes {
1362+
return Some((k_str, v));
1363+
}
1364+
}
1365+
}
1366+
}
1367+
1368+
None
1369+
}
1370+
1371+
/// Fallback SIMD implementation for non-x86_64 architectures
1372+
#[cfg(not(target_arch = "x86_64"))]
1373+
fn simd_string_compare<'a>(
1374+
&self,
1375+
_key: &str,
1376+
_pairs: &'a [(Value, Value)],
1377+
) -> Option<(&'a str, &'a Self)> {
1378+
None
1379+
}
1380+
1381+
/// Optimized linear search with length pre-check
1382+
#[inline]
1383+
fn linear_search_optimized<'a>(
1384+
&self,
1385+
key: &str,
1386+
pairs: &'a [(Value, Value)],
1387+
) -> Option<(&'a str, &'a Self)> {
1388+
let key_len = key.len();
1389+
1390+
for (k, v) in pairs {
1391+
if let Some(k_str) = k.as_str() {
1392+
// Length pre-check before string comparison
1393+
if k_str.len() == key_len && k_str == key {
1394+
return Some((k_str, v));
1395+
}
1396+
}
1397+
}
1398+
None
1399+
}
1400+
11761401
#[inline]
11771402
pub(crate) fn get_key_mut(&mut self, key: &str) -> Option<&mut Self> {
11781403
if let ValueMut::Object(kv) = self.as_mut() {

src/value/object.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ impl Object {
139139
/// ```
140140
#[inline]
141141
pub fn get<Q: AsRef<str>>(&self, key: &Q) -> Option<&Value> {
142-
self.0.get_key(key.as_ref())
142+
self.0.get_key_optimized(key.as_ref())
143143
}
144144

145145
/// Returns `true` if the map contains a value for the specified key.

0 commit comments

Comments
 (0)