From 0917c49b9bf554bed679fe010053de868ed250a2 Mon Sep 17 00:00:00 2001 From: dataphract Date: Tue, 17 May 2022 04:16:53 +0000 Subject: [PATCH] bench: add `bevy_reflect::{List, Map, Struct}` benchmarks (#3690) # Objective Partially addresses #3594. ## Solution This adds basic benchmarks for `List`, `Map`, and `Struct` implementors, both concrete (`Vec`, `HashMap`, and defined struct types) and dynamic (`DynamicList`, `DynamicMap` and `DynamicStruct`). A few insights from the benchmarks (all measurements are local on my machine): - Applying a list with many elements to a list with no elements is slower than applying to a list of the same length: - 3-4x slower when applying to a `Vec` - 5-6x slower when applying to a `DynamicList` I suspect this could be improved by `reserve()`ing the correct length up front, but haven't tested. - Applying a `DynamicMap` to another `Map` is linear in the number of elements, but applying a `HashMap` seems to be at least quadratic. No intuition on this one. - Applying like structs (concrete -> concrete, `DynamicStruct` -> `DynamicStruct`) seems to be faster than applying unlike structs. --- benches/Cargo.toml | 27 +- benches/benches/bevy_reflect/list.rs | 155 ++++++++ benches/benches/bevy_reflect/map.rs | 303 +++++++++++++++ benches/benches/bevy_reflect/struct.rs | 485 +++++++++++++++++++++++++ 4 files changed, 965 insertions(+), 5 deletions(-) create mode 100644 benches/benches/bevy_reflect/list.rs create mode 100644 benches/benches/bevy_reflect/map.rs create mode 100644 benches/benches/bevy_reflect/struct.rs diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 4c8e51c280..eeb69a2413 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -12,7 +12,9 @@ rand = "0.8" rand_chacha = "0.3" criterion = { version = "0.3", features = ["html_reports"] } bevy_ecs = { path = "../crates/bevy_ecs" } +bevy_reflect = { path = "../crates/bevy_reflect" } bevy_tasks = { path = "../crates/bevy_tasks" } +bevy_utils = { path = "../crates/bevy_utils" } [[bench]] name = "archetype_updates" @@ -24,11 +26,6 @@ name = "ecs_bench_suite" path = "benches/bevy_ecs/ecs_bench_suite/mod.rs" harness = false -[[bench]] -name = "system_stage" -path = "benches/bevy_ecs/stages.rs" -harness = false - [[bench]] name = "run_criteria" path = "benches/bevy_ecs/run_criteria.rs" @@ -39,11 +36,31 @@ name = "commands" path = "benches/bevy_ecs/commands.rs" harness = false +[[bench]] +name = "system_stage" +path = "benches/bevy_ecs/stages.rs" +harness = false + [[bench]] name = "world_get" path = "benches/bevy_ecs/world_get.rs" harness = false +[[bench]] +name = "reflect_list" +path = "benches/bevy_reflect/list.rs" +harness = false + +[[bench]] +name = "reflect_map" +path = "benches/bevy_reflect/map.rs" +harness = false + +[[bench]] +name = "reflect_struct" +path = "benches/bevy_reflect/struct.rs" +harness = false + [[bench]] name = "iter" path = "benches/bevy_tasks/iter.rs" diff --git a/benches/benches/bevy_reflect/list.rs b/benches/benches/bevy_reflect/list.rs new file mode 100644 index 0000000000..3c48dd22e4 --- /dev/null +++ b/benches/benches/bevy_reflect/list.rs @@ -0,0 +1,155 @@ +use std::{iter, time::Duration}; + +use bevy_reflect::{DynamicList, List}; +use criterion::{ + black_box, criterion_group, criterion_main, measurement::Measurement, BatchSize, + BenchmarkGroup, BenchmarkId, Criterion, Throughput, +}; + +criterion_group!( + benches, + concrete_list_apply, + concrete_list_clone_dynamic, + dynamic_list_apply, + dynamic_list_push +); +criterion_main!(benches); + +const WARM_UP_TIME: Duration = Duration::from_millis(500); +const MEASUREMENT_TIME: Duration = Duration::from_secs(4); + +// log10 scaling +const SIZES: [usize; 5] = [100_usize, 316, 1000, 3162, 10000]; + +fn list_apply( + group: &mut BenchmarkGroup, + bench_name: &str, + f_base: F1, + f_patch: F3, +) where + M: Measurement, + LBase: List, + LPatch: List, + F1: Fn(usize) -> F2, + F2: Fn() -> LBase, + F3: Fn(usize) -> LPatch, +{ + for size in SIZES { + group.throughput(Throughput::Elements(size as u64)); + + group.bench_with_input( + BenchmarkId::new(bench_name, size), + &size, + |bencher, &size| { + let f_base = f_base(size); + let patch = f_patch(size); + bencher.iter_batched( + || f_base(), + |mut base| base.apply(black_box(&patch)), + BatchSize::SmallInput, + ); + }, + ); + } +} + +fn concrete_list_apply(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("concrete_list_apply"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + let empty_base = |_: usize| || Vec::::new(); + let full_base = |size: usize| move || iter::repeat(0).take(size).collect::>(); + let patch = |size: usize| iter::repeat(1).take(size).collect::>(); + + list_apply(&mut group, "empty_base_concrete_patch", empty_base, patch); + + list_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| { + patch(size).clone_dynamic() + }); + + list_apply(&mut group, "same_len_concrete_patch", full_base, patch); + + list_apply(&mut group, "same_len_dynamic_patch", full_base, |size| { + patch(size).clone_dynamic() + }); + + group.finish(); +} + +fn concrete_list_clone_dynamic(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("concrete_list_clone_dynamic"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + for size in SIZES { + group.throughput(Throughput::Elements(size as u64)); + + group.bench_with_input( + BenchmarkId::from_parameter(size), + &size, + |bencher, &size| { + let v = iter::repeat(0).take(size).collect::>(); + + bencher.iter(|| black_box(&v).clone_dynamic()); + }, + ); + } + + group.finish(); +} + +fn dynamic_list_push(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("dynamic_list_push"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + for size in SIZES { + group.throughput(Throughput::Elements(size as u64)); + + group.bench_with_input( + BenchmarkId::from_parameter(size), + &size, + |bencher, &size| { + let src = iter::repeat(()).take(size).collect::>(); + let dst = DynamicList::default(); + + bencher.iter_batched( + || (src.clone(), dst.clone_dynamic()), + |(src, mut dst)| { + for item in src { + dst.push(item); + } + }, + BatchSize::SmallInput, + ); + }, + ); + } + + group.finish(); +} + +fn dynamic_list_apply(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("dynamic_list_apply"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + let empty_base = |_: usize| || Vec::::new().clone_dynamic(); + let full_base = |size: usize| move || iter::repeat(0).take(size).collect::>(); + let patch = |size: usize| iter::repeat(1).take(size).collect::>(); + + list_apply(&mut group, "empty_base_concrete_patch", empty_base, patch); + + list_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| { + patch(size).clone_dynamic() + }); + + list_apply(&mut group, "same_len_concrete_patch", full_base, patch); + + list_apply(&mut group, "same_len_dynamic_patch", full_base, |size| { + patch(size).clone_dynamic() + }); + + group.finish(); +} diff --git a/benches/benches/bevy_reflect/map.rs b/benches/benches/bevy_reflect/map.rs new file mode 100644 index 0000000000..b5c8a95eee --- /dev/null +++ b/benches/benches/bevy_reflect/map.rs @@ -0,0 +1,303 @@ +use std::{fmt::Write, iter, time::Duration}; + +use bevy_reflect::{DynamicMap, Map}; +use bevy_utils::HashMap; +use criterion::{ + black_box, criterion_group, criterion_main, measurement::Measurement, BatchSize, + BenchmarkGroup, BenchmarkId, Criterion, Throughput, +}; + +criterion_group!( + benches, + concrete_map_apply, + dynamic_map_apply, + dynamic_map_get, + dynamic_map_insert +); +criterion_main!(benches); + +const WARM_UP_TIME: Duration = Duration::from_millis(500); +const MEASUREMENT_TIME: Duration = Duration::from_secs(4); +const SIZES: [usize; 5] = [100, 316, 1000, 3162, 10000]; + +/// Generic benchmark for applying one `Map` to another. +/// +/// `f_base` is a function which takes an input size and produces a generator +/// for new base maps. `f_patch` is a function which produces a map to be +/// applied to the base map. +fn map_apply( + group: &mut BenchmarkGroup, + bench_name: &str, + f_base: F1, + f_patch: F3, +) where + M: Measurement, + MBase: Map, + MPatch: Map, + F1: Fn(usize) -> F2, + F2: Fn() -> MBase, + F3: Fn(usize) -> MPatch, +{ + for size in SIZES { + group.throughput(Throughput::Elements(size as u64)); + group.bench_with_input( + BenchmarkId::new(bench_name, size), + &size, + |bencher, &size| { + let f_base = f_base(size); + bencher.iter_batched( + || (f_base(), f_patch(size)), + |(mut base, patch)| base.apply(black_box(&patch)), + BatchSize::SmallInput, + ); + }, + ); + } +} + +fn concrete_map_apply(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("concrete_map_apply"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + let empty_base = |_: usize| || HashMap::::default(); + + let key_range_base = |size: usize| { + move || { + (0..size as u64) + .zip(iter::repeat(0)) + .collect::>() + } + }; + + let key_range_patch = |size: usize| { + (0..size as u64) + .zip(iter::repeat(1)) + .collect::>() + }; + + let disjoint_patch = |size: usize| { + (size as u64..2 * size as u64) + .zip(iter::repeat(1)) + .collect::>() + }; + + map_apply( + &mut group, + "empty_base_concrete_patch", + empty_base, + key_range_patch, + ); + + map_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| { + key_range_patch(size).clone_dynamic() + }); + + map_apply( + &mut group, + "same_keys_concrete_patch", + key_range_base, + key_range_patch, + ); + + map_apply( + &mut group, + "same_keys_dynamic_patch", + key_range_base, + |size| key_range_patch(size).clone_dynamic(), + ); + + map_apply( + &mut group, + "disjoint_keys_concrete_patch", + key_range_base, + disjoint_patch, + ); + + map_apply( + &mut group, + "disjoint_keys_dynamic_patch", + key_range_base, + |size| disjoint_patch(size).clone_dynamic(), + ); +} + +fn u64_to_n_byte_key(k: u64, n: usize) -> String { + let mut key = String::with_capacity(n); + write!(&mut key, "{}", k).unwrap(); + + // Pad key to n bytes. + key.extend(iter::repeat('\0').take(n - key.len())); + key +} + +fn dynamic_map_apply(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("dynamic_map_apply"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + let empty_base = |_: usize| || DynamicMap::default(); + + let key_range_base = |size: usize| { + move || { + (0..size as u64) + .zip(iter::repeat(0)) + .collect::>() + .clone_dynamic() + } + }; + + let key_range_patch = |size: usize| { + (0..size as u64) + .zip(iter::repeat(1)) + .collect::>() + }; + + let disjoint_patch = |size: usize| { + (size as u64..2 * size as u64) + .zip(iter::repeat(1)) + .collect::>() + }; + + map_apply( + &mut group, + "empty_base_concrete_patch", + empty_base, + key_range_patch, + ); + + map_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| { + key_range_patch(size).clone_dynamic() + }); + + map_apply( + &mut group, + "same_keys_concrete_patch", + key_range_base, + key_range_patch, + ); + + map_apply( + &mut group, + "same_keys_dynamic_patch", + key_range_base, + |size| key_range_patch(size).clone_dynamic(), + ); + + map_apply( + &mut group, + "disjoint_keys_concrete_patch", + key_range_base, + disjoint_patch, + ); + + map_apply( + &mut group, + "disjoint_keys_dynamic_patch", + key_range_base, + |size| disjoint_patch(size).clone_dynamic(), + ); +} + +fn dynamic_map_get(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("dynamic_map_get"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + for size in SIZES { + group.throughput(Throughput::Elements(size as u64)); + group.bench_with_input( + BenchmarkId::new("u64_keys", size), + &size, + |bencher, &size| { + let mut map = DynamicMap::default(); + for i in 0..size as u64 { + map.insert(i, i); + } + + bencher.iter(|| { + for i in 0..size as u64 { + let key = black_box(i); + black_box(assert!(map.get(&key).is_some())); + } + }); + }, + ); + } + + for size in SIZES { + group.throughput(Throughput::Elements(size as u64)); + group.bench_with_input( + BenchmarkId::new("64_byte_keys", size), + &size, + |bencher, &size| { + let mut map = DynamicMap::default(); + let mut keys = Vec::with_capacity(size); + for i in 0..size as u64 { + let key = u64_to_n_byte_key(i, 64); + map.insert(key.clone(), i); + keys.push(key); + } + + bencher.iter(|| { + for i in 0..size { + let key = black_box(&keys[i]); + assert!(map.get(key).is_some()); + } + }); + }, + ); + } +} + +fn dynamic_map_insert(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("dynamic_map_insert"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + for size in SIZES { + group.throughput(Throughput::Elements(size as u64)); + group.bench_with_input( + BenchmarkId::new("u64_keys", size), + &size, + |bencher, &size| { + bencher.iter_batched( + || DynamicMap::default(), + |mut map| { + for i in 0..size as u64 { + let key = black_box(i); + black_box(map.insert(key, i)); + } + }, + BatchSize::SmallInput, + ); + }, + ); + } + + for size in SIZES { + group.throughput(Throughput::Elements(size as u64)); + group.bench_with_input( + BenchmarkId::new("64_byte_keys", size), + &size, + |bencher, &size| { + let mut keys = Vec::with_capacity(size); + for i in 0..size { + let key = u64_to_n_byte_key(i as u64, 64); + keys.push(key); + } + + bencher.iter_batched( + || (DynamicMap::default(), keys.clone()), + |(mut map, keys)| { + for (i, key) in keys.into_iter().enumerate() { + let key = black_box(key); + map.insert(key, i); + } + }, + BatchSize::SmallInput, + ); + }, + ); + } +} diff --git a/benches/benches/bevy_reflect/struct.rs b/benches/benches/bevy_reflect/struct.rs new file mode 100644 index 0000000000..a879828ab4 --- /dev/null +++ b/benches/benches/bevy_reflect/struct.rs @@ -0,0 +1,485 @@ +use std::time::Duration; + +use bevy_reflect::{DynamicStruct, GetField, Reflect, Struct}; +use criterion::{ + black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput, +}; + +criterion_group!( + benches, + concrete_struct_apply, + concrete_struct_field, + dynamic_struct_apply, + dynamic_struct_get_field, + dynamic_struct_insert, +); +criterion_main!(benches); + +const WARM_UP_TIME: Duration = Duration::from_millis(500); +const MEASUREMENT_TIME: Duration = Duration::from_secs(4); +const SIZES: [usize; 4] = [16, 32, 64, 128]; + +fn concrete_struct_field(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("concrete_struct_field"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + let structs: [Box; 4] = [ + Box::new(Struct16::default()), + Box::new(Struct32::default()), + Box::new(Struct64::default()), + Box::new(Struct128::default()), + ]; + + for s in structs { + let field_count = s.field_len(); + + group.bench_with_input( + BenchmarkId::from_parameter(field_count), + &s, + |bencher, s| { + let field_names = (0..field_count) + .map(|i| format!("field_{}", i)) + .collect::>(); + + bencher.iter(|| { + for name in field_names.iter() { + s.field(black_box(name)); + } + }); + }, + ); + } +} + +fn concrete_struct_apply(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("concrete_struct_apply"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + // Use functions that produce trait objects of varying concrete types as the + // input to the benchmark. + let inputs: &[fn() -> (Box, Box)] = &[ + || (Box::new(Struct16::default()), Box::new(Struct16::default())), + || (Box::new(Struct32::default()), Box::new(Struct32::default())), + || (Box::new(Struct64::default()), Box::new(Struct64::default())), + || { + ( + Box::new(Struct128::default()), + Box::new(Struct128::default()), + ) + }, + ]; + + for input in inputs { + let field_count = input().0.field_len(); + group.throughput(Throughput::Elements(field_count as u64)); + + group.bench_with_input( + BenchmarkId::new("apply_concrete", field_count), + input, + |bencher, input| { + bencher.iter_batched( + input, + |(mut obj, patch)| obj.apply(black_box(patch.as_ref())), + BatchSize::SmallInput, + ); + }, + ); + } + + for input in inputs { + let field_count = input().0.field_len(); + group.throughput(Throughput::Elements(field_count as u64)); + + group.bench_with_input( + BenchmarkId::new("apply_dynamic", field_count), + input, + |bencher, input| { + bencher.iter_batched( + || { + let (obj, _) = input(); + let patch = obj.clone_dynamic(); + (obj, patch) + }, + |(mut obj, patch)| obj.apply(black_box(&patch)), + BatchSize::SmallInput, + ); + }, + ); + } +} + +fn dynamic_struct_apply(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("dynamic_struct_apply"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + let patches: &[(fn() -> Box, usize)] = &[ + (|| Box::new(Struct16::default()), 16), + (|| Box::new(Struct32::default()), 32), + (|| Box::new(Struct64::default()), 64), + (|| Box::new(Struct128::default()), 128), + ]; + + for (patch, field_count) in patches { + let field_count = *field_count; + group.throughput(Throughput::Elements(field_count as u64)); + + let mut base = DynamicStruct::default(); + for i in 0..field_count { + let field_name = format!("field_{}", i); + base.insert(&field_name, 1u32); + } + + group.bench_with_input( + BenchmarkId::new("apply_concrete", field_count), + &patch, + |bencher, patch| { + bencher.iter_batched( + || (base.clone_dynamic(), patch()), + |(mut base, patch)| base.apply(black_box(&*patch)), + BatchSize::SmallInput, + ); + }, + ); + } + + for field_count in SIZES { + group.throughput(Throughput::Elements(field_count as u64)); + + group.bench_with_input( + BenchmarkId::new("apply_dynamic", field_count), + &field_count, + |bencher, &field_count| { + let mut base = DynamicStruct::default(); + let mut patch = DynamicStruct::default(); + for i in 0..field_count { + let field_name = format!("field_{}", i); + base.insert(&field_name, 0u32); + patch.insert(&field_name, 1u32); + } + + bencher.iter_batched( + || base.clone_dynamic(), + |mut base| base.apply(black_box(&patch)), + BatchSize::SmallInput, + ); + }, + ); + } +} + +fn dynamic_struct_insert(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("dynamic_struct_insert"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + for field_count in SIZES { + group.throughput(Throughput::Elements(field_count as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(field_count), + &field_count, + |bencher, field_count| { + let mut s = DynamicStruct::default(); + for i in 0..*field_count { + let field_name = format!("field_{}", i); + s.insert(&field_name, ()); + } + + let field = format!("field_{}", field_count); + bencher.iter_batched( + || s.clone_dynamic(), + |mut s| { + black_box(s.insert(black_box(&field), ())); + }, + BatchSize::SmallInput, + ); + }, + ); + } + + group.finish(); +} + +fn dynamic_struct_get_field(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("dynamic_struct_get"); + group.warm_up_time(WARM_UP_TIME); + group.measurement_time(MEASUREMENT_TIME); + + for field_count in SIZES { + group.throughput(Throughput::Elements(field_count as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(field_count), + &field_count, + |bencher, field_count| { + let mut s = DynamicStruct::default(); + for i in 0..*field_count { + let field_name = format!("field_{}", i); + s.insert(&field_name, ()); + } + + let field = black_box("field_63"); + bencher.iter(|| { + black_box(s.get_field::<()>(field)); + }); + }, + ); + } +} + +#[derive(Clone, Default, Reflect)] +struct Struct16 { + field_0: u32, + field_1: u32, + field_2: u32, + field_3: u32, + field_4: u32, + field_5: u32, + field_6: u32, + field_7: u32, + field_8: u32, + field_9: u32, + field_10: u32, + field_11: u32, + field_12: u32, + field_13: u32, + field_14: u32, + field_15: u32, +} + +#[derive(Clone, Default, Reflect)] +struct Struct32 { + field_0: u32, + field_1: u32, + field_2: u32, + field_3: u32, + field_4: u32, + field_5: u32, + field_6: u32, + field_7: u32, + field_8: u32, + field_9: u32, + field_10: u32, + field_11: u32, + field_12: u32, + field_13: u32, + field_14: u32, + field_15: u32, + field_16: u32, + field_17: u32, + field_18: u32, + field_19: u32, + field_20: u32, + field_21: u32, + field_22: u32, + field_23: u32, + field_24: u32, + field_25: u32, + field_26: u32, + field_27: u32, + field_28: u32, + field_29: u32, + field_30: u32, + field_31: u32, +} + +#[derive(Clone, Default, Reflect)] +struct Struct64 { + field_0: u32, + field_1: u32, + field_2: u32, + field_3: u32, + field_4: u32, + field_5: u32, + field_6: u32, + field_7: u32, + field_8: u32, + field_9: u32, + field_10: u32, + field_11: u32, + field_12: u32, + field_13: u32, + field_14: u32, + field_15: u32, + field_16: u32, + field_17: u32, + field_18: u32, + field_19: u32, + field_20: u32, + field_21: u32, + field_22: u32, + field_23: u32, + field_24: u32, + field_25: u32, + field_26: u32, + field_27: u32, + field_28: u32, + field_29: u32, + field_30: u32, + field_31: u32, + field_32: u32, + field_33: u32, + field_34: u32, + field_35: u32, + field_36: u32, + field_37: u32, + field_38: u32, + field_39: u32, + field_40: u32, + field_41: u32, + field_42: u32, + field_43: u32, + field_44: u32, + field_45: u32, + field_46: u32, + field_47: u32, + field_48: u32, + field_49: u32, + field_50: u32, + field_51: u32, + field_52: u32, + field_53: u32, + field_54: u32, + field_55: u32, + field_56: u32, + field_57: u32, + field_58: u32, + field_59: u32, + field_60: u32, + field_61: u32, + field_62: u32, + field_63: u32, +} + +#[derive(Clone, Default, Reflect)] +struct Struct128 { + field_0: u32, + field_1: u32, + field_2: u32, + field_3: u32, + field_4: u32, + field_5: u32, + field_6: u32, + field_7: u32, + field_8: u32, + field_9: u32, + field_10: u32, + field_11: u32, + field_12: u32, + field_13: u32, + field_14: u32, + field_15: u32, + field_16: u32, + field_17: u32, + field_18: u32, + field_19: u32, + field_20: u32, + field_21: u32, + field_22: u32, + field_23: u32, + field_24: u32, + field_25: u32, + field_26: u32, + field_27: u32, + field_28: u32, + field_29: u32, + field_30: u32, + field_31: u32, + field_32: u32, + field_33: u32, + field_34: u32, + field_35: u32, + field_36: u32, + field_37: u32, + field_38: u32, + field_39: u32, + field_40: u32, + field_41: u32, + field_42: u32, + field_43: u32, + field_44: u32, + field_45: u32, + field_46: u32, + field_47: u32, + field_48: u32, + field_49: u32, + field_50: u32, + field_51: u32, + field_52: u32, + field_53: u32, + field_54: u32, + field_55: u32, + field_56: u32, + field_57: u32, + field_58: u32, + field_59: u32, + field_60: u32, + field_61: u32, + field_62: u32, + field_63: u32, + field_64: u32, + field_65: u32, + field_66: u32, + field_67: u32, + field_68: u32, + field_69: u32, + field_70: u32, + field_71: u32, + field_72: u32, + field_73: u32, + field_74: u32, + field_75: u32, + field_76: u32, + field_77: u32, + field_78: u32, + field_79: u32, + field_80: u32, + field_81: u32, + field_82: u32, + field_83: u32, + field_84: u32, + field_85: u32, + field_86: u32, + field_87: u32, + field_88: u32, + field_89: u32, + field_90: u32, + field_91: u32, + field_92: u32, + field_93: u32, + field_94: u32, + field_95: u32, + field_96: u32, + field_97: u32, + field_98: u32, + field_99: u32, + field_100: u32, + field_101: u32, + field_102: u32, + field_103: u32, + field_104: u32, + field_105: u32, + field_106: u32, + field_107: u32, + field_108: u32, + field_109: u32, + field_110: u32, + field_111: u32, + field_112: u32, + field_113: u32, + field_114: u32, + field_115: u32, + field_116: u32, + field_117: u32, + field_118: u32, + field_119: u32, + field_120: u32, + field_121: u32, + field_122: u32, + field_123: u32, + field_124: u32, + field_125: u32, + field_126: u32, + field_127: u32, +}