1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 15:36:03 +00:00

more fair benchmark

This commit is contained in:
mat 2023-08-27 21:20:38 -05:00
parent 8e633b2b46
commit cd49fe5f64
4 changed files with 383 additions and 25 deletions

View file

@ -1,4 +1,5 @@
use std::{
collections::HashMap,
fs::File,
io::{Cursor, Read},
};
@ -24,22 +25,26 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
let mut group = c.benchmark_group(filename);
group.throughput(Throughput::Bytes(input.len() as u64));
{
// compare to make sure they decode equally
let azalea_nbt =
azalea_items_from_nbt(azalea_nbt::Nbt::read(&mut Cursor::new(input)).unwrap()).unwrap();
let graphite_nbt =
graphite_items_from_nbt(graphite_binary::nbt::decode::read(&mut &input[..]).unwrap())
.unwrap();
let simdnbt_nbt =
simdnbt_items_from_nbt(simdnbt::Nbt::new(&mut Cursor::new(input)).unwrap().unwrap())
.unwrap();
assert_eq!(azalea_nbt, graphite_nbt);
assert_eq!(azalea_nbt, simdnbt_nbt);
}
group.bench_function("azalea_parse", |b| {
b.iter(|| {
let input = black_box(input);
let nbt = black_box(azalea_nbt::Nbt::read(&mut Cursor::new(input)).unwrap());
black_box(
nbt.as_compound()
.unwrap()
.get("")
.unwrap()
.as_compound()
.unwrap()
.get("PersistentId")
.unwrap()
.as_int()
.unwrap(),
);
black_box(azalea_items_from_nbt(nbt));
})
});
@ -48,7 +53,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
let input = black_box(input);
let nbt = black_box(graphite_binary::nbt::decode::read(&mut &input[..]).unwrap());
// black_box(nbt);
black_box(nbt.find_root("PersistentId").unwrap().as_int());
black_box(graphite_items_from_nbt(nbt));
})
});
@ -57,8 +62,7 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
let input = black_box(input);
let nbt = black_box(simdnbt::Nbt::new(&mut Cursor::new(input)));
let nbt = nbt.unwrap().unwrap();
// black_box(nbt);
black_box(nbt.int("PersistentId").unwrap());
black_box(simdnbt_items_from_nbt(nbt));
})
});
@ -102,14 +106,290 @@ pub fn bench_read_file(filename: &str, c: &mut Criterion) {
// });
}
#[derive(Clone, PartialEq, Debug)]
pub struct Item {
pub id: i16,
pub damage: i16,
pub count: i8,
pub head_texture_id: Option<String>,
pub skyblock_id: Option<String>,
pub reforge: Option<String>,
pub display: ItemDisplay,
pub enchantments: HashMap<String, i32>,
pub timestamp: Option<String>,
}
#[derive(Clone, PartialEq, Debug)]
pub struct ItemDisplay {
pub name: String,
pub lore: Vec<String>,
pub has_glint: bool,
pub color: Option<i32>,
}
fn simdnbt_items_from_nbt(nbt: simdnbt::Nbt) -> Option<Vec<Option<Item>>> {
let mut items = Vec::new();
for item_nbt in nbt
.list("i")
.and_then(|list| list.compounds())
.unwrap_or_default()
{
// check if "id" is present, if not, skip
if !item_nbt.contains("id") {
// this just means the item isn't present
items.push(None);
continue;
}
let item_tag = item_nbt.compound("tag")?;
let item_extra_attributes = item_tag.compound("ExtraAttributes");
let item_display = item_tag.compound("display");
items.push(Some(Item {
id: item_nbt.short("id")?,
damage: item_nbt.short("Damage")?,
count: item_nbt.byte("Count")?,
head_texture_id: item_tag
.compound("SkullOwner")
.and_then(|skull_owner| skull_owner.compound("Properties"))
.and_then(|properties| properties.list("textures"))
.and_then(|textures| textures.compounds())
.and_then(|textures| textures.get(0))
.and_then(|texture| texture.string("Value"))
// the real program does some base64+json decoding here but that's unnecessary for the benchmark
.map(|value| value.to_string()),
skyblock_id: item_extra_attributes
.and_then(|e| e.string("id"))
.map(|id| id.to_string()),
reforge: item_extra_attributes
.and_then(|e| e.string("modifier"))
.map(|id| id.to_string()),
display: ItemDisplay {
name: item_display
.and_then(|d| d.string("Name"))
.map(|n| n.to_string())
.unwrap_or_default(),
lore: item_display
.and_then(|d| d.list("Lore"))
.and_then(|l| l.strings())
.map(|l| l.iter().map(|s| s.to_string()).collect())
.unwrap_or_default(),
color: item_display.and_then(|d| d.int("color")),
has_glint: item_extra_attributes
.map(|e| e.contains("ench"))
.unwrap_or_default(),
},
enchantments: item_extra_attributes
.and_then(|e| e.compound("enchantments"))
.map(|e| {
e.iter()
.map(|(k, v)| (k.to_string(), v.int().unwrap_or_default()))
.collect()
})
.unwrap_or_default(),
timestamp: item_extra_attributes
.and_then(|e| e.string("timestamp"))
.map(|t| t.to_string()),
}));
}
Some(items)
}
fn azalea_items_from_nbt(nbt: azalea_nbt::Nbt) -> Option<Vec<Option<Item>>> {
let mut items = Vec::new();
let azalea_nbt::NbtList::Compound(item_compound_list) = nbt
.as_compound()
.and_then(|c| c.get(""))
.and_then(|c| c.as_compound())
.and_then(|c| c.get("i"))
.and_then(|i| i.as_list())?
else {
return None;
};
for item_nbt in item_compound_list {
// check if "id" is present, if not, skip
let Some(id) = item_nbt.get("id") else {
// this just means the item isn't present
items.push(None);
continue;
};
// let item_tag = item_nbt.compound("tag")?;
// let item_extra_attributes = item_tag.compound("ExtraAttributes");
// let item_display = item_tag.compound("display");
let item_tag = item_nbt.get("tag")?.as_compound()?;
let item_extra_attributes = item_tag
.get("ExtraAttributes")
.and_then(|e| e.as_compound());
let item_display = item_tag.get("display").and_then(|d| d.as_compound());
items.push(Some(Item {
id: *id.as_short()?,
damage: *item_nbt.get("Damage")?.as_short()?,
count: *item_nbt.get("Count")?.as_byte()?,
head_texture_id: item_tag
.get("SkullOwner")
.and_then(|skull_owner| skull_owner.as_compound())
.and_then(|skull_owner| {
skull_owner
.get("Properties")
.and_then(|properties| properties.as_compound())
})
.and_then(|properties| {
properties
.get("textures")
.and_then(|textures| textures.as_list())
})
.and_then(|textures| {
if let azalea_nbt::NbtList::Compound(textures) = textures {
textures
.get(0)
.and_then(|texture| texture.get("Value"))
.and_then(|value| value.as_string().cloned())
.map(|string| string.to_string())
} else {
None
}
}),
skyblock_id: item_extra_attributes
.and_then(|e| e.get("id"))
.and_then(|id| id.as_string().cloned())
.map(|string| string.to_string()),
reforge: item_extra_attributes
.and_then(|e| e.get("modifier"))
.and_then(|id| id.as_string().cloned())
.map(|string| string.to_string()),
display: ItemDisplay {
name: item_display
.and_then(|d| d.get("Name"))
.and_then(|n| n.as_string().cloned())
.unwrap_or_default()
.to_string(),
lore: item_display
.and_then(|d| d.get("Lore"))
.and_then(|l| l.as_list())
.and_then(|l| {
if let azalea_nbt::NbtList::String(l) = l {
Some(l)
} else {
None
}
})
.map(|l| l.iter().map(|s| s.to_string()).collect())
.unwrap_or_default(),
color: item_display
.and_then(|d| d.get("color"))
.and_then(|c| c.as_int())
.copied(),
has_glint: item_extra_attributes
.map(|e| e.get("ench").is_some())
.unwrap_or_default(),
},
enchantments: item_extra_attributes
.and_then(|e| e.get("enchantments"))
.and_then(|e| e.as_compound())
.map(|e| {
e.iter()
.map(|(k, v)| (k.to_string(), v.as_int().copied().unwrap_or_default()))
.collect()
})
.unwrap_or_default(),
timestamp: item_extra_attributes
.and_then(|e| e.get("timestamp"))
.and_then(|t| t.as_string().cloned())
.map(|string| string.to_string()),
}));
}
Some(items)
}
fn graphite_items_from_nbt(nbt: graphite_binary::nbt::NBT) -> Option<Vec<Option<Item>>> {
let mut items = Vec::new();
for item_nbt in nbt.find_root("i").and_then(|i| nbt.iter(i))? {
// check if "id" is present, if not, skip
let Some(id) = nbt.find(item_nbt, "id") else {
// this just means the item isn't present
items.push(None);
continue;
};
let item_tag = nbt.find(item_nbt, "tag")?;
let item_extra_attributes = nbt.find(item_tag, "ExtraAttributes");
let item_display = nbt.find(item_tag, "display");
items.push(Some(Item {
id: id.as_short()?,
damage: nbt.find(item_nbt, "Damage")?.as_short()?,
count: nbt.find(item_nbt, "Count")?.as_byte()?,
head_texture_id: nbt
.find(item_tag, "SkullOwner")
.and_then(|skull_owner| nbt.find(skull_owner, "Properties"))
.and_then(|properties| nbt.find(properties, "textures"))
.and_then(|textures| nbt.iter(textures)?.next())
.and_then(|texture| nbt.find(texture, "Value"))
// the real program does some base64+json decoding here but that's unnecessary for the benchmark
.and_then(|value| value.as_string().cloned()),
skyblock_id: item_extra_attributes
.and_then(|e| nbt.find(e, "id"))
.and_then(|id| id.as_string().cloned()),
reforge: item_extra_attributes
.and_then(|e| nbt.find(e, "modifier"))
.and_then(|id| id.as_string().cloned()),
display: ItemDisplay {
name: item_display
.and_then(|d| nbt.find(d, "Name"))
.and_then(|n| n.as_string().cloned())
.unwrap_or_default(),
lore: item_display
.and_then(|d| nbt.find(d, "Lore"))
.and_then(|l| nbt.iter(l))
.map(|l| l.filter_map(|s| s.as_string().cloned()).collect())
.unwrap_or_default(),
color: item_display
.and_then(|d| nbt.find(d, "color"))
.and_then(|c| c.as_int()),
has_glint: item_extra_attributes
.map(|e| nbt.find(e, "ench").is_some())
.unwrap_or_default(),
},
enchantments: item_extra_attributes
.and_then(|e| nbt.find(e, "enchantments"))
.and_then(|e| nbt.iter(e))
.map(|e| {
e.map(|n| {
(
nbt.find(n, "key")
.and_then(|k| k.as_string())
.cloned()
.unwrap_or_default(),
nbt.find(n, "value")
.and_then(|v| v.as_int())
.unwrap_or_default(),
)
})
.collect()
})
.unwrap_or_default(),
timestamp: item_extra_attributes
.and_then(|e| nbt.find(e, "timestamp"))
.and_then(|t| t.as_string().cloned()),
}));
}
Some(items)
}
fn bench(c: &mut Criterion) {
// bench_read_file("tests/hello_world.nbt", c);
// bench_read_file("tests/bigtest.nbt", c);
bench_read_file("tests/simple_player.dat", c);
// bench_read_file("tests/complex_player.dat", c);
// bench_read_file("tests/level.dat", c);
// bench_read_file("tests/stringtest.nbt", c);
// bench_read_file("tests/inttest.nbt", c);
bench_read_file("tests/realworld.nbt", c);
}
criterion_group!(benches, bench);

View file

@ -90,7 +90,7 @@ pub const COMPOUND_ID: u8 = 10;
pub const INT_ARRAY_ID: u8 = 11;
pub const LONG_ARRAY_ID: u8 = 12;
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct CompoundTag<'a> {
values: Vec<(&'a Mutf8Str, Tag<'a>)>,
}
@ -300,10 +300,14 @@ impl<'a> CompoundTag<'a> {
}
None
}
pub fn iter(&self) -> impl Iterator<Item = (&Mutf8Str, &Tag<'a>)> {
self.values.iter().map(|(k, v)| (*k, v))
}
}
fn read_u8_array<'a>(data: &mut Cursor<&'a [u8]>) -> Result<&'a [u8], Error> {
Ok(read_with_u32_length(data, 1)?)
read_with_u32_length(data, 1)
}
fn read_i8_array<'a>(data: &mut Cursor<&'a [u8]>) -> Result<&'a [i8], Error> {
Ok(slice_u8_into_i8(read_u8_array(data)?))
@ -377,6 +381,80 @@ pub enum Tag<'a> {
IntArray(Vec<i32>),
LongArray(Vec<i64>),
}
impl<'a> Tag<'a> {
pub fn byte(&self) -> Option<i8> {
match self {
Tag::Byte(byte) => Some(*byte),
_ => None,
}
}
pub fn short(&self) -> Option<i16> {
match self {
Tag::Short(short) => Some(*short),
_ => None,
}
}
pub fn int(&self) -> Option<i32> {
match self {
Tag::Int(int) => Some(*int),
_ => None,
}
}
pub fn long(&self) -> Option<i64> {
match self {
Tag::Long(long) => Some(*long),
_ => None,
}
}
pub fn float(&self) -> Option<f32> {
match self {
Tag::Float(float) => Some(*float),
_ => None,
}
}
pub fn double(&self) -> Option<f64> {
match self {
Tag::Double(double) => Some(*double),
_ => None,
}
}
pub fn byte_array(&self) -> Option<&[u8]> {
match self {
Tag::ByteArray(byte_array) => Some(byte_array),
_ => None,
}
}
pub fn string(&self) -> Option<&Mutf8Str> {
match self {
Tag::String(string) => Some(string),
_ => None,
}
}
pub fn list(&self) -> Option<&ListTag<'a>> {
match self {
Tag::List(list) => Some(list),
_ => None,
}
}
pub fn compound(&self) -> Option<&CompoundTag<'a>> {
match self {
Tag::Compound(compound) => Some(compound),
_ => None,
}
}
pub fn int_array(&self) -> Option<&[i32]> {
match self {
Tag::IntArray(int_array) => Some(int_array),
_ => None,
}
}
pub fn long_array(&self) -> Option<&[i64]> {
match self {
Tag::LongArray(long_array) => Some(long_array),
_ => None,
}
}
}
#[derive(Debug, Default)]
pub enum ListTag<'a> {
#[default]

View file

@ -33,7 +33,7 @@ impl Mutf8Str {
true
}
pub fn from_str<'a>(s: &'a str) -> Cow<'a, Mutf8Str> {
pub fn from_str(s: &str) -> Cow<Mutf8Str> {
match mutf8::encode(s) {
Cow::Borrowed(b) => Cow::Borrowed(Mutf8Str::from_slice(b)),
Cow::Owned(o) => Cow::Owned(Mutf8String { vec: o }),

BIN
tests/realworld.nbt Normal file

Binary file not shown.