Skip to content

Commit 58b8bbc

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

File tree

2 files changed

+230
-2
lines changed

2 files changed

+230
-2
lines changed

src/value/node.rs

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ use core::mem::size_of;
33
use std::collections::BTreeMap;
44
use std::{
55
alloc::Layout,
6+
collections::HashMap,
67
fmt::{Debug, Display, Formatter},
78
mem::{transmute, ManuallyDrop},
89
ptr::NonNull,
910
slice::from_raw_parts,
1011
str::from_utf8_unchecked,
11-
sync::Arc,
12+
sync::{Arc, Mutex},
1213
};
1314

1415
#[cfg(not(feature = "sort_keys"))]
@@ -17,6 +18,17 @@ use faststr::FastStr;
1718
use ref_cast::RefCast;
1819
use serde::ser::{Serialize, SerializeMap, SerializeSeq};
1920

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()));
28+
29+
#[cfg(feature = "sort_keys")]
30+
use std::collections::BTreeMap;
31+
2032
use super::{
2133
object::Pair,
2234
shared::Shared,
@@ -1155,6 +1167,11 @@ impl Value {
11551167
self.get_key_value(key).map(|(_, v)| v)
11561168
}
11571169

1170+
#[inline]
1171+
pub(crate) fn get_key_optimized(&self, key: &str) -> Option<&Self> {
1172+
self.get_key_value_optimized(key).map(|(_, v)| v)
1173+
}
1174+
11581175
pub(crate) fn get_key_value(&self, key: &str) -> Option<(&str, &Self)> {
11591176
debug_assert!(self.is_object());
11601177
let ref_inner = self.as_ref2();
@@ -1173,6 +1190,217 @@ impl Value {
11731190
None
11741191
}
11751192

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