Skip to content

Commit d40257f

Browse files
committed
Optimize sorted serde table traversal
1 parent ca76c54 commit d40257f

3 files changed

Lines changed: 64 additions & 2 deletions

File tree

‎benches/serde.rs‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use std::collections::BTreeMap;
12
use std::time::Duration;
23

3-
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
4+
use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main};
45

56
use mlua::prelude::*;
67

@@ -76,6 +77,29 @@ fn decode_json(c: &mut Criterion) {
7677
});
7778
}
7879

80+
fn sorted_map_traversal(c: &mut Criterion) {
81+
let lua = Lua::new();
82+
let mut group = c.benchmark_group("sorted map traversal");
83+
for size in [0, 1, 2, 3, 4, 7, 8, 64, 4096] {
84+
let table = lua
85+
.create_table_from((0..size).map(|i| (format!("key_{i:08}"), i)))
86+
.unwrap();
87+
let value = LuaValue::Table(table);
88+
group.bench_function(BenchmarkId::new("serialize", size), |b| {
89+
b.iter(|| serde_json::to_string(&value.to_serializable().sort_keys(true)).unwrap());
90+
});
91+
group.bench_function(BenchmarkId::new("from_value", size), |b| {
92+
b.iter(|| {
93+
lua.from_value_with::<BTreeMap<String, i64>>(
94+
value.clone(),
95+
LuaDeserializeOptions::new().sort_keys(true),
96+
)
97+
.unwrap()
98+
});
99+
});
100+
}
101+
}
102+
79103
criterion_group! {
80104
name = benches;
81105
config = Criterion::default()
@@ -85,6 +109,7 @@ criterion_group! {
85109
targets =
86110
encode_json,
87111
decode_json,
112+
sorted_map_traversal,
88113
}
89114

90115
criterion_main!(benches);

‎src/serde/de.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ pub(crate) enum MapPairs<'a> {
518518
impl<'a> MapPairs<'a> {
519519
pub(crate) fn new(t: &'a Table, sort_keys: bool) -> Result<Self> {
520520
if sort_keys {
521-
let mut pairs = t.pairs::<Value, Value>().collect::<Result<Vec<_>>>()?;
521+
let mut pairs = t.collect_pairs()?;
522522
pairs.sort_by(|(a, _), (b, _)| b.sort_cmp(a)); // reverse order as we pop values from the end
523523
Ok(MapPairs::Vec(pairs))
524524
} else {

‎src/table.rs‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,43 @@ impl Table {
10681068
None
10691069
}
10701070

1071+
#[cfg(feature = "serde")]
1072+
pub(crate) fn collect_pairs(&self) -> Result<Vec<(Value, Value)>> {
1073+
let mut pairs = Vec::new();
1074+
1075+
#[cfg(not(feature = "luau"))]
1076+
unsafe {
1077+
const LIMIT: c_int = 8;
1078+
let lua = self.0.lua.lock();
1079+
let state = lua.state();
1080+
let _sg = StackGuard::new(state);
1081+
check_stack(state, 2 * LIMIT + 2)?;
1082+
1083+
lua.push_ref(&self.0);
1084+
let table_index = ffi::lua_gettop(state);
1085+
ffi::lua_pushnil(state);
1086+
// Finish small traversals before conversions or allocations can reenter Lua
1087+
for count in 0..LIMIT {
1088+
if ffi::lua_next(state, table_index) == 0 {
1089+
pairs.reserve_exact(count as usize);
1090+
for i in 0..count {
1091+
let index = table_index + 1 + 2 * i;
1092+
let key = lua.try_stack_value(index, None)?;
1093+
pairs.push((key, lua.try_stack_value(index + 1, None)?));
1094+
}
1095+
return Ok(pairs);
1096+
}
1097+
ffi::lua_pushvalue(state, -2);
1098+
}
1099+
}
1100+
1101+
self.for_each(|key, value| {
1102+
pairs.push((key, value));
1103+
Ok(())
1104+
})?;
1105+
Ok(pairs)
1106+
}
1107+
10711108
#[cfg(not(feature = "luau"))]
10721109
#[inline]
10731110
unsafe fn next(state: *mut ffi::lua_State) -> Result<bool> {

0 commit comments

Comments
 (0)