Skip to content

Commit c60cac0

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

File tree

2 files changed

+235
-4
lines changed

2 files changed

+235
-4
lines changed

src/value/node.rs

Lines changed: 234 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
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+
27+
// Type aliases to reduce complexity
28+
type HashIndex = HashMap<String, usize>;
29+
type CacheEntry = (HashIndex, u32);
30+
type HashIndexCache = HashMap<usize, CacheEntry>;
31+
32+
static HASH_INDEX_CACHE: LazyLock<Mutex<HashIndexCache>> =
33+
LazyLock::new(|| Mutex::new(HashMap::new()));
1934

2035
use super::{
2136
object::Pair,
@@ -1155,6 +1170,11 @@ impl Value {
11551170
self.get_key_value(key).map(|(_, v)| v)
11561171
}
11571172

1173+
#[inline]
1174+
pub(crate) fn get_key_optimized(&self, key: &str) -> Option<&Self> {
1175+
self.get_key_value_optimized(key).map(|(_, v)| v)
1176+
}
1177+
11581178
pub(crate) fn get_key_value(&self, key: &str) -> Option<(&str, &Self)> {
11591179
debug_assert!(self.is_object());
11601180
let ref_inner = self.as_ref2();
@@ -1173,6 +1193,217 @@ impl Value {
11731193
None
11741194
}
11751195

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