package xarr import "base:runtime" import "core:fmt" import "core:mem/virtual" import "core:strings" import "core:testing" import "core:time" @(test) test_msb :: proc(t: ^testing.T) { testing.expect_value(t, msb(0), -1) testing.expect_value(t, msb(1), 0) testing.expect_value(t, msb(2), 1) testing.expect_value(t, msb(3), 1) testing.expect_value(t, msb(4), 2) testing.expect_value(t, msb(5), 2) testing.expect_value(t, msb(6), 2) testing.expect_value(t, msb(7), 2) testing.expect_value(t, msb(8), 3) testing.expect_value(t, msb(16), 4) testing.expect_value(t, msb(64), 6) } @(test) test_chunk_sizes :: proc(t: ^testing.T) { testing.expect_value(t, chunk_size(0), BASE_CHUNK_SIZE) testing.expect_value(t, chunk_size(1), BASE_CHUNK_SIZE) testing.expect_value(t, chunk_size(2), BASE_CHUNK_SIZE * 2) testing.expect_value(t, chunk_size(3), BASE_CHUNK_SIZE * 4) testing.expect_value(t, chunk_size(4), BASE_CHUNK_SIZE * 8) } @(test) test_capacity_from_mask :: proc(t: ^testing.T) { testing.expect_value(t, capacity_from_allocated_mask(0b1), chunk_size(0)) testing.expect_value(t, capacity_from_allocated_mask(0b11), chunk_size(0) + chunk_size(1)) testing.expect_value( t, capacity_from_allocated_mask(0b111), chunk_size(0) + chunk_size(1) + chunk_size(2), ) testing.expect_value( t, capacity_from_allocated_mask(0b1111), chunk_size(0) + chunk_size(1) + chunk_size(2) + chunk_size(3), ) testing.expect_value( t, capacity_from_allocated_mask(0b11111), chunk_size(0) + chunk_size(1) + chunk_size(2) + chunk_size(3) + chunk_size(4), ) } @(test) test_indexing :: proc(t: ^testing.T) { chunk, idx := translate_index(0) testing.expect_value(t, chunk, 0) testing.expect_value(t, idx, 0) chunk, idx = translate_index(BASE_CHUNK_SIZE - 1) testing.expect_value(t, chunk, 0) testing.expect_value(t, idx, BASE_CHUNK_SIZE - 1) chunk, idx = translate_index(BASE_CHUNK_SIZE) testing.expect_value(t, chunk, 1) testing.expect_value(t, idx, 0) chunk, idx = translate_index(BASE_CHUNK_SIZE * 3 - 1) testing.expect_value(t, chunk, 2) testing.expect_value(t, idx, BASE_CHUNK_SIZE - 1) chunk, idx = translate_index(BASE_CHUNK_SIZE * 5) testing.expect_value(t, chunk, 3) testing.expect_value(t, idx, BASE_CHUNK_SIZE) } @(test) test_basic :: proc(t: ^testing.T) { a: Xarr(int) defer delete(&a) NUM :: 10000 RUNS :: 4 for _ in 0 ..< RUNS { defer clear(&a) for i in 0 ..< NUM { append(&a, i) } testing.expect_value(t, a.len, NUM) for i in 0 ..< NUM { testing.expect_value(t, get(a, i), i) } } } @(test) test_remove :: proc(t: ^testing.T) { a: Xarr(int) defer delete(&a) append(&a, 1, 2, 3, 4) unordered_remove(&a, 1) testing.expect_value(t, a.len, 3) testing.expect_value(t, get(a, 0), 1) testing.expect_value(t, get(a, 1), 4) testing.expect_value(t, get(a, 2), 3) } @(test) test_iterator :: proc(t: ^testing.T) { a: Xarr(int) defer delete(&a) append(&a, 0, 1, 2, 3, 4) it := iterator(&a) for e, i in iterator_next(&it) { testing.expect_value(t, e^, i) } } @(test) test_soa :: proc(t: ^testing.T) { My_Struct :: struct { x, y, z: f32, } a: Xarr(My_Struct, true) defer delete(&a) append(&a, My_Struct{x = 1, y = 2, z = 3}) testing.expect_value(t, get(a, 0), My_Struct{x = 1, y = 2, z = 3}) } @(test) benchmark_dyn_array_append :: proc(t: ^testing.T) { str: strings.Builder strings.builder_init(&str, context.allocator) defer { fmt.println(strings.to_string(str)) strings.builder_destroy(&str) } { arena: virtual.Arena arena_err := virtual.arena_init_static(&arena) testing.expect_value(t, arena_err, nil) defer virtual.arena_destroy(&arena) name := "Dynamic Array Append" options := &time.Benchmark_Options { rounds = 10_000, bytes = 100_000, setup = setup_bench, bench = benchmark_dynamic_array_append, // teardown = teardown_bench, } err := time.benchmark(options, virtual.arena_allocator(&arena)) testing.expect_value(t, err, nil) benchmark_print(&str, name, options) } } @(test) benchmark_xarr_append :: proc(t: ^testing.T) { str: strings.Builder strings.builder_init(&str, context.allocator) defer { fmt.println(strings.to_string(str)) strings.builder_destroy(&str) } { arena: virtual.Arena arena_err := virtual.arena_init_static(&arena) testing.expect_value(t, arena_err, nil) defer virtual.arena_destroy(&arena) name := "Xarr Append" options := &time.Benchmark_Options { rounds = 10_000, bytes = 1_000_000, setup = setup_bench, bench = benchmar_xarr_append, } err := time.benchmark(options, virtual.arena_allocator(&arena)) testing.expect_value(t, err, nil) benchmark_print(&str, name, options) } } ITERATION_ARRAY_NUM :: 1_000_000 ITERATION_ROUNDS :: 10_000 @(test) benchmark_xarr_index_iteration :: proc(t: ^testing.T) { str: strings.Builder strings.builder_init(&str, context.allocator) defer { fmt.println(strings.to_string(str)) strings.builder_destroy(&str) } arr: Xarr(int) defer delete(&arr) for i in 0 ..< ITERATION_ARRAY_NUM { append(&arr, i) } total_sum: int diff: time.Duration { time.SCOPED_TICK_DURATION(&diff) for _ in 0 ..< ITERATION_ROUNDS { sum: int for i in 0 ..< arr.len { sum += get(arr, i) } total_sum += sum } } options := &time.Benchmark_Options{rounds = 10_000, bytes = 1_000_000} options.count = options.rounds options.processed = size_of(int) * arr.len * options.rounds options.duration = diff times_per_second := f64(time.Second) / f64(diff) options.rounds_per_second = times_per_second * f64(options.count) options.megabytes_per_second = f64(options.processed) / f64(1024 * 1024) * times_per_second benchmark_print(&str, "Xarr Index Iteration", options) } @(test) benchmark_xarr_chunk_iteration :: proc(t: ^testing.T) { str: strings.Builder strings.builder_init(&str, context.allocator) defer { fmt.println(strings.to_string(str)) strings.builder_destroy(&str) } arr: Xarr(int) defer delete(&arr) for i in 0 ..< ITERATION_ARRAY_NUM { append(&arr, i) } total_sum: int diff: time.Duration { time.SCOPED_TICK_DURATION(&diff) for _ in 0 ..< ITERATION_ROUNDS { sum: int it := chunk_iterator(&arr) for chunk, base_idx in chunk_iterator_next(&it) { for i in 0 ..< len(chunk) { sum += chunk[i] } } total_sum += sum } } options := &time.Benchmark_Options{rounds = 10_000, bytes = 1_000_000} options.count = options.rounds options.processed = size_of(int) * arr.len * options.rounds options.duration = diff times_per_second := f64(time.Second) / f64(diff) options.rounds_per_second = times_per_second * f64(options.count) options.megabytes_per_second = f64(options.processed) / f64(1024 * 1024) * times_per_second benchmark_print(&str, "Xarr Chunk Iteration", options) } @(test) benchmark_slice_index_iteration :: proc(t: ^testing.T) { str: strings.Builder strings.builder_init(&str, context.allocator) defer { fmt.println(strings.to_string(str)) strings.builder_destroy(&str) } slice := make([]int, ITERATION_ARRAY_NUM) for i in 0 ..< ITERATION_ARRAY_NUM { slice[i] = i } total_sum: int diff: time.Duration { time.SCOPED_TICK_DURATION(&diff) for _ in 0 ..< ITERATION_ROUNDS { sum: int for i in 0 ..< len(slice) { sum += slice[i] } total_sum += sum } } options := &time.Benchmark_Options{rounds = 10_000, bytes = 1_000_000} options.count = options.rounds options.processed = size_of(int) * len(slice) * options.rounds options.duration = diff times_per_second := f64(time.Second) / f64(diff) options.rounds_per_second = times_per_second * f64(options.count) options.megabytes_per_second = f64(options.processed) / f64(1024 * 1024) * times_per_second benchmark_print(&str, "Slice Index Iteration", options) } setup_bench :: proc( options: ^time.Benchmark_Options, allocator := context.allocator, ) -> ( err: time.Benchmark_Error, ) { assert(options != nil) options.input = make([]u8, options.bytes, allocator) for &b, i in options.input { b = u8(i & 0xff) } return nil if len(options.input) == options.bytes else .Allocation_Error } teardown_bench :: proc( options: ^time.Benchmark_Options, allocator := context.allocator, ) -> ( err: time.Benchmark_Error, ) { assert(options != nil) runtime.delete(options.input) return nil } benchmark_dynamic_array_append :: proc( options: ^time.Benchmark_Options, allocator := context.allocator, ) -> ( err: time.Benchmark_Error, ) { buf := options.input for _ in 0 ..< options.rounds { arr: [dynamic]u8 defer runtime.delete(arr) for byte in buf { runtime.append(&arr, byte) } } options.count = options.rounds options.processed = options.rounds * options.bytes return nil } benchmar_xarr_append :: proc( options: ^time.Benchmark_Options, allocator := context.allocator, ) -> ( err: time.Benchmark_Error, ) { buf := options.input for _ in 0 ..< options.rounds { arr: Xarr(u8) defer delete(&arr) for byte in buf { append(&arr, byte) } } options.count = options.rounds options.processed = options.rounds * options.bytes return nil } benchmar_xarr_iterate :: proc( options: ^time.Benchmark_Options, allocator := context.allocator, ) -> ( err: time.Benchmark_Error, ) { buf := options.input for _ in 0 ..< options.rounds { arr: Xarr(u8) defer delete(&arr) for byte in buf { append(&arr, byte) } } options.count = options.rounds options.processed = options.rounds * options.bytes return nil } benchmark_print :: proc( str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location, ) { fmt.sbprintfln( str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", name, options.rounds, options.processed, time.duration_nanoseconds(options.duration), options.rounds_per_second, options.megabytes_per_second, ) }