448 lines
9.7 KiB
Odin

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,
)
}