Skip to content

Commit d9f8fa9

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

File tree

2 files changed

+231
-2
lines changed

2 files changed

+231
-2
lines changed

src/value/node.rs

Lines changed: 230 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
use core::mem::size_of;
2+
#[cfg(target_arch = "x86_64")]
3+
use std::arch::x86_64::*;
24
#[cfg(feature = "sort_keys")]
35
use std::collections::BTreeMap;
6+
7+
use std::sync::LazyLock;
48
use std::{
59
alloc::Layout,
10+
collections::HashMap,
611
fmt::{Debug, Display, Formatter},
712
mem::{transmute, ManuallyDrop},
813
ptr::NonNull,
914
slice::from_raw_parts,
1015
str::from_utf8_unchecked,
11-
sync::Arc,
16+
sync::{Arc, Mutex},
1217
};
1318

1419
#[cfg(not(feature = "sort_keys"))]
@@ -17,6 +22,14 @@ use faststr::FastStr;
1722
use ref_cast::RefCast;
1823
use serde::ser::{Serialize, SerializeMap, SerializeSeq};
1924

25+
// Type aliases to reduce complexity
26+
type HashIndex = HashMap<String, usize>;
27+
type CacheEntry = (HashIndex, u32);
28+
type HashIndexCache = HashMap<usize, CacheEntry>;
29+
30+
static HASH_INDEX_CACHE: LazyLock<Mutex<HashIndexCache>> =
31+
LazyLock::new(|| Mutex::new(HashMap::new()));
32+
2033
use super::{
2134
object::Pair,
2235
shared::Shared,
@@ -1155,6 +1168,11 @@ impl Value {
11551168
self.get_key_value(key).map(|(_, v)| v)
11561169
}
11571170

1171+
#[inline]
1172+
pub(crate) fn get_key_optimized(&self, key: &str) -> Option<&Self> {
1173+
self.get_key_value_optimized(key).map(|(_, v)| v)
1174+
}
1175+
11581176
pub(crate) fn get_key_value(&self, key: &str) -> Option<(&str, &Self)> {
11591177
debug_assert!(self.is_object());
11601178
let ref_inner = self.as_ref2();
@@ -1173,6 +1191,217 @@ impl Value {
11731191
None
11741192
}
11751193

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