Skip to content

Commit 4f2432b

Browse files
authored
Merge pull request #105 from oschwald/greg/optimize
Performance improvements
2 parents ffd065c + 5dfedbc commit 4f2432b

File tree

5 files changed

+48
-17
lines changed

5 files changed

+48
-17
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Change Log
22

3+
## 0.27.1 - 2025-12-18
4+
5+
- Performance improvement: Skipped UTF-8 validation for map keys during
6+
deserialization. This significantly speeds up full record decoding by
7+
treating keys as raw bytes when matching against struct fields.
8+
- Performance improvement: Optimized tree traversal by reducing bounds checks
9+
during node reading.
10+
311
## 0.27.0 - 2025-11-28
412

513
This release includes significant API changes. See [UPGRADING.md](UPGRADING.md)

src/decoder.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -446,19 +446,26 @@ impl<'de> Decoder<'de> {
446446
}
447447
}
448448

449-
/// Reads a string directly, following pointers if needed.
450-
pub(crate) fn read_string(&mut self) -> DecodeResult<&'de str> {
449+
/// Reads a string's bytes directly, following pointers if needed.
450+
/// Does NOT validate UTF-8.
451+
pub(crate) fn read_str_as_bytes(&mut self) -> DecodeResult<&'de [u8]> {
451452
let (size, type_num) = self.size_and_type();
452453
if type_num == TYPE_POINTER {
453454
// Pointer
454455
let new_ptr = self.decode_pointer(size);
455456
let saved_ptr = self.current_ptr;
456457
self.current_ptr = new_ptr;
457-
let result = self.read_string();
458+
let result = self.read_str_as_bytes();
458459
self.current_ptr = saved_ptr;
459460
result
460461
} else if type_num == TYPE_STRING {
461-
self.decode_string(size)
462+
let new_offset = self.current_ptr + size;
463+
if new_offset > self.buf.len() {
464+
return Err(self.invalid_db_error("string length exceeds buffer"));
465+
}
466+
let bytes = &self.buf[self.current_ptr..new_offset];
467+
self.current_ptr = new_offset;
468+
Ok(bytes)
462469
} else {
463470
Err(self.invalid_db_error(&format!("expected string, got type {type_num}")))
464471
}
@@ -595,10 +602,23 @@ impl<'de: 'a, 'a> de::Deserializer<'de> for &'a mut Decoder<'de> {
595602
visitor.visit_enum(EnumAccessor { de: self })
596603
}
597604

605+
fn deserialize_identifier<V>(self, visitor: V) -> DecodeResult<V::Value>
606+
where
607+
V: Visitor<'de>,
608+
{
609+
let (_, type_num) = self.peek_type()?;
610+
if type_num == TYPE_STRING {
611+
let bytes = self.read_str_as_bytes()?;
612+
visitor.visit_borrowed_bytes(bytes)
613+
} else {
614+
self.decode_any(visitor)
615+
}
616+
}
617+
598618
forward_to_deserialize_any! {
599619
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
600620
bytes byte_buf unit unit_struct newtype_struct seq tuple
601-
tuple_struct map struct identifier
621+
tuple_struct map struct
602622
}
603623
}
604624

src/reader.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,8 @@ impl<'de, S: AsRef<[u8]>> Reader<S> {
450450
let val = match self.metadata.record_size {
451451
24 => {
452452
let offset = base_offset + index * 3;
453-
(buf[offset] as usize) << 16
454-
| (buf[offset + 1] as usize) << 8
455-
| buf[offset + 2] as usize
453+
let bytes = &buf[offset..offset + 3];
454+
(bytes[0] as usize) << 16 | (bytes[1] as usize) << 8 | bytes[2] as usize
456455
}
457456
28 => {
458457
let middle = if index != 0 {
@@ -461,17 +460,19 @@ impl<'de, S: AsRef<[u8]>> Reader<S> {
461460
(buf[base_offset + 3] & 0xF0) >> 4
462461
};
463462
let offset = base_offset + index * 4;
463+
let bytes = &buf[offset..offset + 3];
464464
(middle as usize) << 24
465-
| (buf[offset] as usize) << 16
466-
| (buf[offset + 1] as usize) << 8
467-
| buf[offset + 2] as usize
465+
| (bytes[0] as usize) << 16
466+
| (bytes[1] as usize) << 8
467+
| bytes[2] as usize
468468
}
469469
32 => {
470470
let offset = base_offset + index * 4;
471-
(buf[offset] as usize) << 24
472-
| (buf[offset + 1] as usize) << 16
473-
| (buf[offset + 2] as usize) << 8
474-
| buf[offset + 3] as usize
471+
let bytes = &buf[offset..offset + 4];
472+
(bytes[0] as usize) << 24
473+
| (bytes[1] as usize) << 16
474+
| (bytes[2] as usize) << 8
475+
| bytes[3] as usize
475476
}
476477
s => {
477478
return Err(MaxMindDbError::invalid_database(format!(

src/reader_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,7 @@ fn test_ignored_any() {
12121212
let _ = env_logger::try_init();
12131213

12141214
// Struct that only reads some fields, ignoring others via IgnoredAny
1215+
#[allow(dead_code)]
12151216
#[derive(Deserialize, Debug)]
12161217
struct PartialRead {
12171218
utf8_string: String,

src/result.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,10 @@ impl<'a, S: AsRef<[u8]>> LookupResult<'a, S> {
252252
let size = decoder.consume_map_header().map_err(with_path)?;
253253

254254
let mut found = false;
255+
let key_bytes = key.as_bytes();
255256
for _ in 0..size {
256-
let k = decoder.read_string().map_err(with_path)?;
257-
if k == key {
257+
let k = decoder.read_str_as_bytes().map_err(with_path)?;
258+
if k == key_bytes {
258259
found = true;
259260
break;
260261
} else {

0 commit comments

Comments
 (0)