Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,127 @@
|
||||
# V Benchmarks
|
||||
|
||||
All benchmarks compiled with `v -prod` on Apple M5, 16 GB RAM, macOS (arm64).
|
||||
V version: 0.5.1.
|
||||
|
||||
## GC: Boehm vs VGC
|
||||
|
||||
Compares Boehm GC (`-gc boehm`) against V's built-in concurrent tri-color
|
||||
mark-and-sweep (`-gc vgc`).
|
||||
5 iterations per test, median reported.
|
||||
|
||||
```
|
||||
v run bench/bench_gc.v
|
||||
```
|
||||
|
||||
```
|
||||
test boehm vgc ratio
|
||||
———————————————————————————————————————————— ————————— ————————— —————————
|
||||
small allocs (1000000x string) 39 ms 48 ms 1.23x
|
||||
tree build+walk (depth=18, 10x) 48 ms 118 ms 2.46x
|
||||
array grow (100x 100000 pushes) 9 ms 26 ms 2.89x
|
||||
map insert (20x 10k entries) 20 ms 27 ms 1.35x
|
||||
mixed workload (50 rounds) 10 ms 16 ms 1.60x
|
||||
|
||||
heap usage:
|
||||
boehm: 29856 KB allocated, 29296 KB free
|
||||
vgc: 131072 KB allocated, 0 KB free
|
||||
```
|
||||
|
||||
Boehm is still 1.2x-2.9x faster across these workloads and uses ~4x less heap.
|
||||
|
||||
## Closures
|
||||
|
||||
Measures closure creation, invocation, multi-threaded creation, and memory overhead.
|
||||
|
||||
```
|
||||
v -prod -o /tmp/bench_closure bench/bench_closure.v && /tmp/bench_closure
|
||||
```
|
||||
|
||||
```
|
||||
| Test Name | Iterations | Time(ms) | Ops/sec |
|
||||
|---------------------------|------------|----------|--------------|
|
||||
| Normal Function Call | 100000000 | 0 | +inf Mop/s |
|
||||
| Small Closure Creation | 10000000 | 188 | 53.19 Mop/s |
|
||||
| Medium Closure Creation | 10000000 | 376 | 26.60 Mop/s |
|
||||
| Large Closure Creation | 1000000 | 121 | 8.26 Mop/s |
|
||||
| Small Closure Call | 100000000 | 136 | 735.29 Mop/s |
|
||||
| Medium Closure Call | 100000000 | 133 | 751.88 Mop/s |
|
||||
| Large Closure Call | 10000000 | 16 | 625.00 Mop/s |
|
||||
| Multi-threaded Creation | 1000000 | 95 | 10.53 Mop/s |
|
||||
```
|
||||
|
||||
Memory: ~69 bytes per closure (medium, 4 captured vars). Closure calls are ~625-750 Mop/s.
|
||||
|
||||
## String Deduplication
|
||||
|
||||
Compares four deduplication strategies on 10,000 strings with ~30% duplicates.
|
||||
|
||||
```
|
||||
v -prod -o /tmp/bench_string_dedup bench/bench_string_dedup.v && /tmp/bench_string_dedup
|
||||
```
|
||||
|
||||
```
|
||||
Method 1 (basic array) 33 ms 7000 unique
|
||||
Method 2 (pre-allocated array) 27 ms 7000 unique
|
||||
Method 3 (map) 0 ms 7000 unique
|
||||
Method 4 (set) 0 ms 7000 unique
|
||||
```
|
||||
|
||||
Maps and sets are orders of magnitude faster than linear array search for deduplication.
|
||||
|
||||
## Vectors (Boids Simulation)
|
||||
|
||||
N-body boids simulation with 10,000 entities: cohesion, separation, and alignment.
|
||||
|
||||
```
|
||||
v -prod -o /tmp/bench_vectors bench/vectors/vectors.v && /tmp/bench_vectors
|
||||
```
|
||||
|
||||
```
|
||||
~50 ms per run (after warmup)
|
||||
```
|
||||
|
||||
## Crypto: ECDSA
|
||||
|
||||
Key generation, signing, and verification (1,000 iterations each).
|
||||
|
||||
```
|
||||
v -prod -o /tmp/bench_ecdsa bench/crypto/ecdsa/ecdsa.v && /tmp/bench_ecdsa
|
||||
```
|
||||
|
||||
```
|
||||
Average key generation time: 9 µs
|
||||
Average sign time: 11 µs
|
||||
Average verify time: 30 µs
|
||||
```
|
||||
|
||||
## SOA Structs (V2 cleanc only)
|
||||
|
||||
Compares Array-of-Structs vs Struct-of-Arrays memory layout for a 16-field
|
||||
particle system (500k particles). Uses V2's `@[soa]` attribute which auto-generates
|
||||
separate contiguous arrays per field for better cache utilization.
|
||||
|
||||
```
|
||||
./cmd/v2/v2 -prod -backend cleanc bench/bench_soa_structs.v -o bench/bench_soa_structs
|
||||
./bench/bench_soa_structs
|
||||
```
|
||||
|
||||
```
|
||||
build particles
|
||||
aos: 20 ms soa push: 125 ms soa indexed: 37 ms
|
||||
|
||||
sum x only
|
||||
aos: 19 ms soa: 12 ms speedup: 1.58x
|
||||
|
||||
sum x/y/z/life (4 of 16 fields)
|
||||
aos: 14 ms soa: 10 ms speedup: 1.40x
|
||||
|
||||
sum all 16 fields
|
||||
aos: 11 ms soa: 16 ms speedup: 0.69x
|
||||
|
||||
integrate position/velocity/life
|
||||
aos: 10 ms soa: 13 ms speedup: 0.77x
|
||||
```
|
||||
|
||||
SOA is 1.4x-1.6x faster for partial field access (fewer cache lines touched).
|
||||
When all fields are accessed or mutated, AOS wins due to less pointer indirection.
|
||||
@@ -0,0 +1,278 @@
|
||||
module main
|
||||
|
||||
import time
|
||||
import sync
|
||||
import os
|
||||
import runtime
|
||||
import v.util.version
|
||||
|
||||
// Define closure type alias
|
||||
type ClosureFN = fn () int
|
||||
|
||||
// Test closures with different capture sizes
|
||||
fn create_closure_small() ClosureFN {
|
||||
a := 0
|
||||
return fn [a] () int {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
fn create_closure_medium() ClosureFN {
|
||||
a, b, c, d := 1, 2, 3, 4
|
||||
return fn [a, b, c, d] () int {
|
||||
return a + b - c * d
|
||||
}
|
||||
}
|
||||
|
||||
struct LargeData {
|
||||
array [10]int
|
||||
}
|
||||
|
||||
fn create_closure_large() ClosureFN {
|
||||
data := LargeData{
|
||||
array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]!
|
||||
}
|
||||
return fn [data] () int {
|
||||
mut sum := 0
|
||||
for i in 0 .. 10 {
|
||||
sum += data.array[i]
|
||||
}
|
||||
return sum
|
||||
}
|
||||
}
|
||||
|
||||
// Result structs
|
||||
struct TestResult {
|
||||
test_name string
|
||||
iterations int
|
||||
time_ms i64
|
||||
ops_per_sec f64 // Operations per second
|
||||
notes string
|
||||
}
|
||||
|
||||
struct MemoryResult {
|
||||
test_name string
|
||||
count int
|
||||
start_mem_kb int
|
||||
end_mem_kb int
|
||||
delta_kb int
|
||||
bytes_per_closure int
|
||||
check_sum int
|
||||
}
|
||||
|
||||
// Benchmark group - returns result structs
|
||||
fn benchmark_closure_creation(iterations int) []TestResult {
|
||||
mut results := []TestResult{}
|
||||
|
||||
// Test small closure creation
|
||||
mut start := time.ticks()
|
||||
for _ in 0 .. iterations {
|
||||
_ = create_closure_small()
|
||||
}
|
||||
small_time := time.ticks() - start
|
||||
mut ops_per_sec := f64(iterations) * 1000.0 / f64(small_time)
|
||||
results << TestResult{'Small Closure Creation', iterations, small_time, ops_per_sec, ''}
|
||||
|
||||
// Test medium closure creation
|
||||
start = time.ticks()
|
||||
for _ in 0 .. iterations {
|
||||
_ = create_closure_medium()
|
||||
}
|
||||
medium_time := time.ticks() - start
|
||||
ops_per_sec = f64(iterations) * 1000.0 / f64(medium_time)
|
||||
results << TestResult{'Medium Closure Creation', iterations, medium_time, ops_per_sec, ''}
|
||||
|
||||
// Test large closure creation
|
||||
large_iter := iterations / 10
|
||||
start = time.ticks()
|
||||
for _ in 0 .. large_iter {
|
||||
_ = create_closure_large()
|
||||
}
|
||||
large_time := time.ticks() - start
|
||||
ops_per_sec = f64(large_iter) * 1000.0 / f64(large_time)
|
||||
results << TestResult{'Large Closure Creation', large_iter, large_time, ops_per_sec, ''} //, "Equivalent iterations: ${iterations/10}"}
|
||||
return results
|
||||
}
|
||||
|
||||
fn benchmark_closure_call(iterations int) []TestResult {
|
||||
mut results := []TestResult{}
|
||||
|
||||
closure_small := create_closure_small()
|
||||
closure_medium := create_closure_medium()
|
||||
closure_large := create_closure_large()
|
||||
|
||||
// Test small closure call
|
||||
mut start := time.ticks()
|
||||
for _ in 0 .. iterations {
|
||||
_ = closure_small()
|
||||
}
|
||||
small_time := time.ticks() - start
|
||||
mut ops_per_sec := f64(iterations) * 1000.0 / f64(small_time)
|
||||
results << TestResult{'Small Closure Call', iterations, small_time, ops_per_sec, ''}
|
||||
|
||||
// Test medium closure call
|
||||
start = time.ticks()
|
||||
for _ in 0 .. iterations {
|
||||
_ = closure_medium()
|
||||
}
|
||||
medium_time := time.ticks() - start
|
||||
ops_per_sec = f64(iterations) * 1000.0 / f64(medium_time)
|
||||
results << TestResult{'Medium Closure Call', iterations, medium_time, ops_per_sec, ''}
|
||||
|
||||
// Test large closure call
|
||||
large_iter := iterations / 10
|
||||
start = time.ticks()
|
||||
for _ in 0 .. large_iter {
|
||||
_ = closure_large()
|
||||
}
|
||||
large_time := time.ticks() - start
|
||||
ops_per_sec = f64(large_iter) * 1000.0 / f64(large_time)
|
||||
results << TestResult{'Large Closure Call', large_iter, large_time, ops_per_sec, ''}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
fn benchmark_threaded_creation(threads int, iterations_per_thread int) TestResult {
|
||||
total_iterations := threads * iterations_per_thread
|
||||
|
||||
mut wg := sync.new_waitgroup()
|
||||
wg.add(threads)
|
||||
|
||||
start := time.ticks()
|
||||
|
||||
for _ in 0 .. threads {
|
||||
go fn [mut wg, iterations_per_thread] () {
|
||||
defer { wg.done() }
|
||||
for _ in 0 .. iterations_per_thread {
|
||||
_ = create_closure_medium()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.wait()
|
||||
elapsed := time.ticks() - start
|
||||
ops_per_sec := f64(total_iterations) * 1000.0 / f64(elapsed)
|
||||
|
||||
return TestResult{
|
||||
test_name: 'Multi-threaded Creation'
|
||||
iterations: total_iterations
|
||||
time_ms: elapsed
|
||||
ops_per_sec: ops_per_sec
|
||||
notes: 'Threads: ${threads} Iterations per thread: ${iterations_per_thread}'
|
||||
}
|
||||
}
|
||||
|
||||
fn baseline_call_performance(iterations int) TestResult {
|
||||
start := time.ticks()
|
||||
for _ in 0 .. iterations {
|
||||
_ = normal_function()
|
||||
}
|
||||
elapsed := time.ticks() - start
|
||||
ops_per_sec := f64(iterations) * 1000.0 / f64(elapsed)
|
||||
|
||||
return TestResult{
|
||||
test_name: 'Normal Function Call'
|
||||
iterations: iterations
|
||||
time_ms: elapsed
|
||||
ops_per_sec: ops_per_sec
|
||||
notes: 'Baseline'
|
||||
}
|
||||
}
|
||||
|
||||
fn benchmark_memory_usage(count int) MemoryResult {
|
||||
mut closures := []ClosureFN{}
|
||||
start_mem := runtime.used_memory() or { panic(err) }
|
||||
|
||||
for i in 0 .. count {
|
||||
the_closure := create_closure_medium()
|
||||
closures << the_closure
|
||||
|
||||
if i % 1000 == 0 {
|
||||
_ = the_closure()
|
||||
}
|
||||
}
|
||||
|
||||
end_mem := runtime.used_memory() or { panic(err) }
|
||||
delta := int(end_mem) - int(start_mem)
|
||||
bytes_per_closure := delta / count
|
||||
|
||||
// Calculate verification sum
|
||||
mut check_sum := 0
|
||||
n := if closures.len < 100 { closures.len } else { 100 }
|
||||
for idx in 0 .. n {
|
||||
check_sum += closures[idx]()
|
||||
}
|
||||
|
||||
return MemoryResult{
|
||||
test_name: 'Closure Memory Overhead'
|
||||
count: count
|
||||
start_mem_kb: int(start_mem / 1024)
|
||||
end_mem_kb: int(end_mem / 1024)
|
||||
delta_kb: delta / 1024
|
||||
bytes_per_closure: bytes_per_closure
|
||||
check_sum: check_sum
|
||||
}
|
||||
}
|
||||
|
||||
fn normal_function() int {
|
||||
return 42
|
||||
}
|
||||
|
||||
// Format performance data for readability
|
||||
fn format_perf(ops_per_sec f64) string {
|
||||
if ops_per_sec >= 1_000_000 {
|
||||
return '${ops_per_sec / 1_000_000:5.2f} Mop/s'
|
||||
} else if ops_per_sec >= 1_000 {
|
||||
return '${ops_per_sec / 1_000:5.2f} Kop/s'
|
||||
} else {
|
||||
return '${ops_per_sec:5.2f} op/s'
|
||||
}
|
||||
}
|
||||
|
||||
fn print_results_table(results []TestResult, title string) {
|
||||
println('|---------------------------|------------|----------|--------------|--------------|')
|
||||
for res in results {
|
||||
perf_str := format_perf(res.ops_per_sec)
|
||||
println('| ${res.test_name:-25} | ${res.iterations:10} | ${res.time_ms:8} | ${perf_str:-12} | ${res.notes:-12} |')
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println('# V Language Closure Performance Benchmark Report')
|
||||
|
||||
// Configurable test parameters
|
||||
base_iter := 100_000_000 // 100 million iterations
|
||||
creation_iter := 10_000_000 // 1 million iterations
|
||||
mem_count := 100_000
|
||||
threads := 8
|
||||
thread_iter := 125_000
|
||||
|
||||
// Execute tests
|
||||
baseline_result := baseline_call_performance(base_iter)
|
||||
creation_results := benchmark_closure_creation(creation_iter)
|
||||
call_results := benchmark_closure_call(base_iter)
|
||||
thread_result := benchmark_threaded_creation(threads, thread_iter)
|
||||
mem_result := benchmark_memory_usage(mem_count)
|
||||
|
||||
// Print result tables
|
||||
println('\n## 1. Closure Performance Analysis')
|
||||
println('| Test Name | Iterations | Time(ms) | Ops/sec | Notes |')
|
||||
print_results_table([baseline_result], '1. Performance Baseline')
|
||||
print_results_table(creation_results, '2. Closure Creation Performance')
|
||||
print_results_table(call_results, '3. Closure Call Performance')
|
||||
print_results_table([thread_result], '4. Multi-threaded Performance')
|
||||
|
||||
// Print memory results
|
||||
println('\n## 2. Memory Overhead Analysis')
|
||||
println('| Test Name | Closure Count | Start Mem(KB) | End Mem(KB) | Delta(KB) | Bytes/Closure |')
|
||||
println('|-------------------------|---------------|---------------|------------|-----------|---------------|')
|
||||
println('| ${mem_result.test_name:-20} | ${mem_result.count:13} | ${mem_result.start_mem_kb:13} | ${mem_result.end_mem_kb:10} | ${mem_result.delta_kb:9} | ${mem_result.bytes_per_closure:13} |')
|
||||
println('\n**Verification Sum: ${mem_result.check_sum}** (Calculated from random sample of 100 closures)')
|
||||
|
||||
println('\n## Test Environment')
|
||||
println('- V Language Version: ${version.full_v_version(false)}')
|
||||
println('- CPU Cores: ${runtime.nr_cpus()}')
|
||||
println('- System Memory: ${runtime.total_memory()! / 1024 / 1024} MB')
|
||||
println('- Operating System: ${os.user_os()}')
|
||||
println('\n> Test Time: ${time.now().format_ss_micro()}')
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
// bench_gc.v - Benchmark comparing Boehm GC vs V GC (vgc)
|
||||
//
|
||||
// Usage (runner mode - compiles and compares both):
|
||||
// v run bench/bench_gc.v
|
||||
//
|
||||
// Usage (individual, to test a specific GC mode):
|
||||
// v -gc boehm -prod -o /tmp/bench_boehm bench/bench_gc.v && /tmp/bench_boehm
|
||||
// v -gc vgc -prod -o /tmp/bench_vgc bench/bench_gc.v && /tmp/bench_vgc
|
||||
module main
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
const n_allocs = 1_000_000
|
||||
const n_iters = 5
|
||||
const tree_depth = 18
|
||||
const tree_rounds = 10
|
||||
const array_rounds = 100
|
||||
const array_size = 100_000
|
||||
|
||||
struct Node {
|
||||
mut:
|
||||
left &Node = unsafe { nil }
|
||||
right &Node = unsafe { nil }
|
||||
value int
|
||||
}
|
||||
|
||||
fn gc_mode_name() string {
|
||||
return $if gcboehm ? {
|
||||
'boehm'
|
||||
} $else $if vgc ? {
|
||||
'vgc'
|
||||
} $else {
|
||||
'none'
|
||||
}
|
||||
}
|
||||
|
||||
// --- Workloads ---
|
||||
|
||||
// Allocate many small objects (typical GC workload)
|
||||
fn bench_small_allocs() (i64, int) {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := 0
|
||||
for _ in 0 .. n_allocs {
|
||||
s := 'hello world ${sum}'
|
||||
sum += s.len
|
||||
}
|
||||
return sw.elapsed().milliseconds(), sum
|
||||
}
|
||||
|
||||
// Build a binary tree (tests pointer-heavy allocation)
|
||||
fn make_tree(depth int) &Node {
|
||||
if depth <= 0 {
|
||||
return &Node{
|
||||
value: 1
|
||||
}
|
||||
}
|
||||
return &Node{
|
||||
left: make_tree(depth - 1)
|
||||
right: make_tree(depth - 1)
|
||||
value: depth
|
||||
}
|
||||
}
|
||||
|
||||
fn tree_sum(n &Node) int {
|
||||
if n == unsafe { nil } {
|
||||
return 0
|
||||
}
|
||||
return n.value + tree_sum(n.left) + tree_sum(n.right)
|
||||
}
|
||||
|
||||
fn bench_tree() (i64, int) {
|
||||
sw := time.new_stopwatch()
|
||||
mut total := 0
|
||||
for _ in 0 .. tree_rounds {
|
||||
t := make_tree(tree_depth)
|
||||
total += tree_sum(t)
|
||||
}
|
||||
return sw.elapsed().milliseconds(), total
|
||||
}
|
||||
|
||||
// Allocate and discard arrays (tests realloc / growing)
|
||||
fn bench_arrays() (i64, int) {
|
||||
sw := time.new_stopwatch()
|
||||
mut total := 0
|
||||
for _ in 0 .. array_rounds {
|
||||
mut arr := []int{cap: 16}
|
||||
for j in 0 .. array_size {
|
||||
arr << j
|
||||
}
|
||||
total += arr.len
|
||||
}
|
||||
return sw.elapsed().milliseconds(), total
|
||||
}
|
||||
|
||||
// Allocate many maps
|
||||
fn bench_maps() (i64, int) {
|
||||
sw := time.new_stopwatch()
|
||||
mut total := 0
|
||||
for round in 0 .. 20 {
|
||||
mut m := map[string]int{}
|
||||
for j in 0 .. 10_000 {
|
||||
m['key_${round}_${j}'] = j
|
||||
}
|
||||
total += m.len
|
||||
}
|
||||
return sw.elapsed().milliseconds(), total
|
||||
}
|
||||
|
||||
// Mixed workload: interleave allocations and collections
|
||||
fn bench_mixed() (i64, int) {
|
||||
sw := time.new_stopwatch()
|
||||
mut total := 0
|
||||
for _ in 0 .. 50 {
|
||||
// strings
|
||||
mut strs := []string{cap: 1000}
|
||||
for j in 0 .. 1000 {
|
||||
strs << 'item_${j}_${'x'.repeat(j % 50)}'
|
||||
}
|
||||
total += strs.len
|
||||
// tree
|
||||
t := make_tree(12)
|
||||
total += tree_sum(t)
|
||||
// array
|
||||
mut arr := []int{cap: 10_000}
|
||||
for j in 0 .. 10_000 {
|
||||
arr << j * 2
|
||||
}
|
||||
total += arr.len
|
||||
}
|
||||
return sw.elapsed().milliseconds(), total
|
||||
}
|
||||
|
||||
// --- Workload runner (compiled with a specific GC) ---
|
||||
|
||||
fn run_workload() {
|
||||
mode := gc_mode_name()
|
||||
for iter in 0 .. n_iters {
|
||||
ms_alloc, sum_alloc := bench_small_allocs()
|
||||
ms_tree, sum_tree := bench_tree()
|
||||
ms_array, sum_array := bench_arrays()
|
||||
ms_map, sum_map := bench_maps()
|
||||
ms_mixed, sum_mixed := bench_mixed()
|
||||
|
||||
// Machine-readable output: iter,test,ms,checksum
|
||||
println('${iter},allocs,${ms_alloc},${sum_alloc}')
|
||||
println('${iter},tree,${ms_tree},${sum_tree}')
|
||||
println('${iter},arrays,${ms_array},${sum_array}')
|
||||
println('${iter},maps,${ms_map},${sum_map}')
|
||||
println('${iter},mixed,${ms_mixed},${sum_mixed}')
|
||||
}
|
||||
usage := gc_heap_usage()
|
||||
println('heap,${usage.heap_size},${usage.free_bytes}')
|
||||
println('gc_mode,${mode}')
|
||||
}
|
||||
|
||||
// --- Runner mode: compile with both GCs and compare ---
|
||||
|
||||
struct BenchData {
|
||||
mut:
|
||||
times [5][]i64 // indexed by test (allocs=0, tree=1, arrays=2, maps=3, mixed=4)
|
||||
heap i64
|
||||
free i64
|
||||
mode string
|
||||
}
|
||||
|
||||
fn test_index(name string) int {
|
||||
return match name {
|
||||
'allocs' { 0 }
|
||||
'tree' { 1 }
|
||||
'arrays' { 2 }
|
||||
'maps' { 3 }
|
||||
'mixed' { 4 }
|
||||
else { -1 }
|
||||
}
|
||||
}
|
||||
|
||||
fn test_name(idx int) string {
|
||||
return ['small allocs (${n_allocs}x string)',
|
||||
'tree build+walk (depth=${tree_depth}, ${tree_rounds}x)',
|
||||
'array grow (${array_rounds}x ${array_size} pushes)', 'map insert (20x 10k entries)',
|
||||
'mixed workload (50 rounds)'][idx]
|
||||
}
|
||||
|
||||
fn median(mut vals []i64) i64 {
|
||||
if vals.len == 0 {
|
||||
return 0
|
||||
}
|
||||
vals.sort(a < b)
|
||||
return vals[vals.len / 2]
|
||||
}
|
||||
|
||||
fn parse_output(output string) BenchData {
|
||||
mut d := BenchData{}
|
||||
for line in output.split_into_lines() {
|
||||
parts := line.split(',')
|
||||
if parts.len < 2 {
|
||||
continue
|
||||
}
|
||||
if parts[0] == 'heap' && parts.len >= 3 {
|
||||
d.heap = parts[1].i64()
|
||||
d.free = parts[2].i64()
|
||||
} else if parts[0] == 'gc_mode' {
|
||||
d.mode = parts[1]
|
||||
} else if parts.len >= 4 {
|
||||
ti := test_index(parts[1])
|
||||
if ti >= 0 {
|
||||
d.times[ti] << parts[2].i64()
|
||||
}
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
fn rpad(s string, width int) string {
|
||||
if s.len >= width {
|
||||
return s
|
||||
}
|
||||
return s + ' '.repeat(width - s.len)
|
||||
}
|
||||
|
||||
fn lpad(s string, width int) string {
|
||||
if s.len >= width {
|
||||
return s
|
||||
}
|
||||
return ' '.repeat(width - s.len) + s
|
||||
}
|
||||
|
||||
fn run_comparison() {
|
||||
v_exe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
src := os.join_path(@VMODROOT, 'bench', 'bench_gc.v')
|
||||
tmp_boehm := os.join_path(os.temp_dir(), 'bench_gc_boehm')
|
||||
tmp_vgc := os.join_path(os.temp_dir(), 'bench_gc_vgc')
|
||||
|
||||
println('=== GC Benchmark: Boehm vs VGC ===')
|
||||
println('')
|
||||
|
||||
// Compile both
|
||||
for gc_mode in ['boehm', 'vgc'] {
|
||||
out := if gc_mode == 'boehm' { tmp_boehm } else { tmp_vgc }
|
||||
print('compiling with -gc ${gc_mode}...')
|
||||
flush_stdout()
|
||||
r := os.execute('${v_exe} -gc ${gc_mode} -prod -o ${out} ${src}')
|
||||
if r.exit_code != 0 {
|
||||
eprintln(' FAILED')
|
||||
eprintln(r.output)
|
||||
exit(1)
|
||||
}
|
||||
println(' ok')
|
||||
}
|
||||
|
||||
println('')
|
||||
|
||||
// Run both
|
||||
mut data := [2]BenchData{}
|
||||
for i, bin in [tmp_boehm, tmp_vgc] {
|
||||
gc_name := if i == 0 { 'boehm' } else { 'vgc' }
|
||||
print('running ${gc_name}...')
|
||||
flush_stdout()
|
||||
r := os.execute('${bin} --run-workload')
|
||||
if r.exit_code != 0 {
|
||||
eprintln(' FAILED')
|
||||
eprintln(r.output)
|
||||
exit(1)
|
||||
}
|
||||
data[i] = parse_output(r.output)
|
||||
println(' done')
|
||||
}
|
||||
|
||||
// Print comparison table
|
||||
println('')
|
||||
println(' ${rpad('test', 44)} ${lpad('boehm', 9)} ${lpad('vgc', 9)} ${lpad('ratio', 9)}')
|
||||
println(' ${'—'.repeat(44)} ${'—'.repeat(9)} ${'—'.repeat(9)} ${'—'.repeat(9)}')
|
||||
|
||||
for ti in 0 .. 5 {
|
||||
mut boehm_vals := data[0].times[ti].clone()
|
||||
mut vgc_vals := data[1].times[ti].clone()
|
||||
mb := median(mut boehm_vals)
|
||||
mv := median(mut vgc_vals)
|
||||
ratio := if mb > 0 { f64(mv) / f64(mb) } else { 0.0 }
|
||||
winner := if ratio < 0.95 {
|
||||
' <-- vgc'
|
||||
} else if ratio > 1.05 {
|
||||
' <-- boehm'
|
||||
} else {
|
||||
''
|
||||
}
|
||||
label := '${ratio:.2f}x${winner}'
|
||||
println(' ${rpad(test_name(ti), 44)} ${lpad('${mb} ms', 9)} ${lpad('${mv} ms', 9)} ${lpad(label,
|
||||
9)}')
|
||||
}
|
||||
|
||||
// Heap usage
|
||||
println('')
|
||||
println(' heap usage:')
|
||||
println(' boehm: ${data[0].heap / 1024} KB allocated, ${data[0].free / 1024} KB free')
|
||||
println(' vgc: ${data[1].heap / 1024} KB allocated, ${data[1].free / 1024} KB free')
|
||||
println('')
|
||||
println(' (ratio < 1.0 = vgc faster, > 1.0 = boehm faster)')
|
||||
|
||||
// Cleanup
|
||||
os.rm(tmp_boehm) or {}
|
||||
os.rm(tmp_vgc) or {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if '--run-workload' in os.args {
|
||||
// We were launched by the runner - execute the workload
|
||||
run_workload()
|
||||
} else {
|
||||
// Act as runner: compile with both GCs and compare
|
||||
run_comparison()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
// bench_soa_structs.v - V2 benchmark for @[soa] structs
|
||||
// Build and run with:
|
||||
// ./cmd/v2/v2 -prod -backend cleanc bench/bench_soa_structs.v -o bench/bench_soa_structs
|
||||
// ./bench/bench_soa_structs
|
||||
module main
|
||||
|
||||
import time
|
||||
|
||||
const particle_count = 500_000
|
||||
const build_rounds = 5
|
||||
const sum_rounds = 48
|
||||
const hot_fields_rounds = 40
|
||||
const full_struct_rounds = 20
|
||||
const integrate_rounds = 20
|
||||
|
||||
@[soa]
|
||||
struct Particle {
|
||||
mut:
|
||||
x f32
|
||||
y f32
|
||||
z f32
|
||||
vx f32
|
||||
vy f32
|
||||
vz f32
|
||||
mass f32
|
||||
temperature f32
|
||||
ax f32
|
||||
ay f32
|
||||
az f32
|
||||
life f32
|
||||
drag f32
|
||||
color_r f32
|
||||
color_g f32
|
||||
color_b f32
|
||||
}
|
||||
|
||||
@[typedef]
|
||||
struct C.Particle_SOA {
|
||||
len int
|
||||
cap int
|
||||
x &f32
|
||||
y &f32
|
||||
z &f32
|
||||
vx &f32
|
||||
vy &f32
|
||||
vz &f32
|
||||
mass &f32
|
||||
temperature &f32
|
||||
ax &f32
|
||||
ay &f32
|
||||
az &f32
|
||||
life &f32
|
||||
drag &f32
|
||||
color_r &f32
|
||||
color_g &f32
|
||||
color_b &f32
|
||||
}
|
||||
|
||||
fn C.Particle_SOA_new(int, int) C.Particle_SOA
|
||||
fn C.Particle_SOA_push(&C.Particle_SOA, Particle)
|
||||
fn C.Particle_SOA_get(C.Particle_SOA, int) Particle
|
||||
fn C.Particle_SOA_free(&C.Particle_SOA)
|
||||
|
||||
struct BenchResult {
|
||||
elapsed_ms i64
|
||||
checksum f64
|
||||
}
|
||||
|
||||
fn make_particle(i int) Particle {
|
||||
base := f32(i % 1024)
|
||||
return Particle{
|
||||
x: base * 0.25
|
||||
y: base * 0.5
|
||||
z: base * 0.75
|
||||
vx: f32(0.001) * f32((i % 13) + 1)
|
||||
vy: f32(0.0015) * f32((i % 17) + 1)
|
||||
vz: f32(0.0005) * f32((i % 19) + 1)
|
||||
mass: f32(1.0) + f32(i % 5) * f32(0.1)
|
||||
temperature: f32(20.0) + f32(i % 7)
|
||||
ax: f32(0.0001) * f32((i % 11) + 1)
|
||||
ay: f32(0.0002) * f32((i % 9) + 1)
|
||||
az: f32(0.0003) * f32((i % 7) + 1)
|
||||
life: f32(100.0) - f32(i % 80)
|
||||
drag: f32(0.98)
|
||||
color_r: f32(i % 255) / f32(255.0)
|
||||
color_g: f32((i * 3) % 255) / f32(255.0)
|
||||
color_b: f32((i * 7) % 255) / f32(255.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_aos() []Particle {
|
||||
mut particles := []Particle{cap: particle_count}
|
||||
for i in 0 .. particle_count {
|
||||
particles << make_particle(i)
|
||||
}
|
||||
return particles
|
||||
}
|
||||
|
||||
fn build_soa() C.Particle_SOA {
|
||||
mut soa := C.Particle_SOA_new(0, particle_count)
|
||||
for i in 0 .. particle_count {
|
||||
C.Particle_SOA_push(&soa, make_particle(i))
|
||||
}
|
||||
return soa
|
||||
}
|
||||
|
||||
fn build_soa_indexed() C.Particle_SOA {
|
||||
mut soa := C.Particle_SOA_new(particle_count, particle_count)
|
||||
unsafe {
|
||||
for i in 0 .. particle_count {
|
||||
p := make_particle(i)
|
||||
soa.x[i] = p.x
|
||||
soa.y[i] = p.y
|
||||
soa.z[i] = p.z
|
||||
soa.vx[i] = p.vx
|
||||
soa.vy[i] = p.vy
|
||||
soa.vz[i] = p.vz
|
||||
soa.mass[i] = p.mass
|
||||
soa.temperature[i] = p.temperature
|
||||
soa.ax[i] = p.ax
|
||||
soa.ay[i] = p.ay
|
||||
soa.az[i] = p.az
|
||||
soa.life[i] = p.life
|
||||
soa.drag[i] = p.drag
|
||||
soa.color_r[i] = p.color_r
|
||||
soa.color_g[i] = p.color_g
|
||||
soa.color_b[i] = p.color_b
|
||||
}
|
||||
}
|
||||
return soa
|
||||
}
|
||||
|
||||
fn bench_build_aos() BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. build_rounds {
|
||||
particles := build_aos()
|
||||
sum += particles.len
|
||||
sum += particles[0].x
|
||||
sum += particles[particles.len - 1].life
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_build_soa() BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. build_rounds {
|
||||
mut soa := build_soa()
|
||||
sum += soa.len
|
||||
unsafe {
|
||||
sum += soa.x[0]
|
||||
sum += soa.life[soa.len - 1]
|
||||
}
|
||||
C.Particle_SOA_free(&soa)
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_build_soa_indexed() BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. build_rounds {
|
||||
mut soa := build_soa_indexed()
|
||||
sum += soa.len
|
||||
unsafe {
|
||||
sum += soa.x[0]
|
||||
sum += soa.life[soa.len - 1]
|
||||
}
|
||||
C.Particle_SOA_free(&soa)
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_sum_x_aos(particles []Particle) BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. sum_rounds {
|
||||
for i in 0 .. particles.len {
|
||||
sum += particles[i].x
|
||||
}
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_sum_x_soa(soa C.Particle_SOA) BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. sum_rounds {
|
||||
unsafe {
|
||||
for i in 0 .. soa.len {
|
||||
sum += soa.x[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_sum_hot_fields_aos(particles []Particle) BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. hot_fields_rounds {
|
||||
for i in 0 .. particles.len {
|
||||
sum += particles[i].x + particles[i].y + particles[i].z + particles[i].life
|
||||
}
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_sum_hot_fields_soa(soa C.Particle_SOA) BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. hot_fields_rounds {
|
||||
unsafe {
|
||||
for i in 0 .. soa.len {
|
||||
sum += soa.x[i] + soa.y[i] + soa.z[i] + soa.life[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_sum_all_fields_aos(particles []Particle) BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. full_struct_rounds {
|
||||
for i in 0 .. particles.len {
|
||||
sum += particles[i].x + particles[i].y + particles[i].z + particles[i].vx +
|
||||
particles[i].vy + particles[i].vz + particles[i].mass + particles[i].temperature +
|
||||
particles[i].ax + particles[i].ay + particles[i].az + particles[i].life +
|
||||
particles[i].drag + particles[i].color_r + particles[i].color_g +
|
||||
particles[i].color_b
|
||||
}
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_sum_all_fields_soa(soa C.Particle_SOA) BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
mut sum := f64(0.0)
|
||||
for _ in 0 .. full_struct_rounds {
|
||||
for i in 0 .. soa.len {
|
||||
p := C.Particle_SOA_get(soa, i)
|
||||
sum += p.x + p.y + p.z + p.vx + p.vy + p.vz + p.mass + p.temperature + p.ax + p.ay +
|
||||
p.az + p.life + p.drag + p.color_r + p.color_g + p.color_b
|
||||
}
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_integrate_aos(mut particles []Particle) BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
for _ in 0 .. integrate_rounds {
|
||||
for i in 0 .. particles.len {
|
||||
particles[i].vx += particles[i].ax
|
||||
particles[i].vy += particles[i].ay
|
||||
particles[i].vz += particles[i].az
|
||||
particles[i].x += particles[i].vx * particles[i].mass
|
||||
particles[i].y += particles[i].vy * particles[i].mass
|
||||
particles[i].z += particles[i].vz * particles[i].mass
|
||||
particles[i].life -= particles[i].drag
|
||||
}
|
||||
}
|
||||
mut sum := f64(0.0)
|
||||
for i in 0 .. particles.len {
|
||||
sum += particles[i].x + particles[i].y + particles[i].z + particles[i].life
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_integrate_soa(mut soa C.Particle_SOA) BenchResult {
|
||||
sw := time.new_stopwatch()
|
||||
unsafe {
|
||||
for _ in 0 .. integrate_rounds {
|
||||
for i in 0 .. soa.len {
|
||||
soa.vx[i] += soa.ax[i]
|
||||
soa.vy[i] += soa.ay[i]
|
||||
soa.vz[i] += soa.az[i]
|
||||
soa.x[i] += soa.vx[i] * soa.mass[i]
|
||||
soa.y[i] += soa.vy[i] * soa.mass[i]
|
||||
soa.z[i] += soa.vz[i] * soa.mass[i]
|
||||
soa.life[i] -= soa.drag[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
mut sum := f64(0.0)
|
||||
unsafe {
|
||||
for i in 0 .. soa.len {
|
||||
sum += soa.x[i] + soa.y[i] + soa.z[i] + soa.life[i]
|
||||
}
|
||||
}
|
||||
return BenchResult{
|
||||
elapsed_ms: sw.elapsed().milliseconds()
|
||||
checksum: sum
|
||||
}
|
||||
}
|
||||
|
||||
fn print_result(name string, aos BenchResult, soa BenchResult) {
|
||||
println(name)
|
||||
println(' aos: ${aos.elapsed_ms} ms, checksum=${aos.checksum}')
|
||||
println(' soa: ${soa.elapsed_ms} ms, checksum=${soa.checksum}')
|
||||
if soa.elapsed_ms > 0 {
|
||||
println(' speedup: ${f64(aos.elapsed_ms) / f64(soa.elapsed_ms)}x')
|
||||
}
|
||||
}
|
||||
|
||||
fn print_build_results(aos BenchResult, soa_push BenchResult, soa_indexed BenchResult) {
|
||||
println('build particles')
|
||||
println(' aos: ${aos.elapsed_ms} ms, checksum=${aos.checksum}')
|
||||
println(' soa push: ${soa_push.elapsed_ms} ms, checksum=${soa_push.checksum}')
|
||||
println(' soa indexed: ${soa_indexed.elapsed_ms} ms, checksum=${soa_indexed.checksum}')
|
||||
if soa_indexed.elapsed_ms > 0 {
|
||||
println(' push vs indexed: ${f64(soa_push.elapsed_ms) / f64(soa_indexed.elapsed_ms)}x')
|
||||
println(' indexed speedup over aos: ${f64(aos.elapsed_ms) / f64(soa_indexed.elapsed_ms)}x')
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println('soa struct bench (v2 cleanc)')
|
||||
println('particle_count=${particle_count}, build_rounds=${build_rounds}, sum_rounds=${sum_rounds}, hot_fields_rounds=${hot_fields_rounds}, full_struct_rounds=${full_struct_rounds}, integrate_rounds=${integrate_rounds}')
|
||||
|
||||
result_build_aos := bench_build_aos()
|
||||
result_build_soa_push := bench_build_soa()
|
||||
result_build_soa_indexed := bench_build_soa_indexed()
|
||||
print_build_results(result_build_aos, result_build_soa_push, result_build_soa_indexed)
|
||||
|
||||
aos_scan := build_aos()
|
||||
soa_scan := build_soa()
|
||||
result_aos_scan := bench_sum_x_aos(aos_scan)
|
||||
result_soa_scan := bench_sum_x_soa(soa_scan)
|
||||
print_result('sum x only', result_aos_scan, result_soa_scan)
|
||||
|
||||
result_aos_hot_fields := bench_sum_hot_fields_aos(aos_scan)
|
||||
result_soa_hot_fields := bench_sum_hot_fields_soa(soa_scan)
|
||||
print_result('sum x/y/z/life', result_aos_hot_fields, result_soa_hot_fields)
|
||||
|
||||
result_aos_full_struct := bench_sum_all_fields_aos(aos_scan)
|
||||
result_soa_full_struct := bench_sum_all_fields_soa(soa_scan)
|
||||
print_result('materialize full struct and sum all fields', result_aos_full_struct,
|
||||
result_soa_full_struct)
|
||||
C.Particle_SOA_free(&soa_scan)
|
||||
|
||||
mut aos_integrate := build_aos()
|
||||
mut soa_integrate := build_soa()
|
||||
result_aos_integrate := bench_integrate_aos(mut aos_integrate)
|
||||
result_soa_integrate := bench_integrate_soa(mut soa_integrate)
|
||||
print_result('integrate position, velocity, and life', result_aos_integrate,
|
||||
result_soa_integrate)
|
||||
C.Particle_SOA_free(&soa_integrate)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Benchmark comparison of four string deduplication methods in V: basic array, pre-allocated array, map, and set
|
||||
module main
|
||||
|
||||
import time
|
||||
import datatypes
|
||||
|
||||
// Method 1: Using basic array (no pre-allocation)
|
||||
struct Context1 {
|
||||
mut:
|
||||
used_str []string
|
||||
}
|
||||
|
||||
fn (mut c Context1) add_used(str string) {
|
||||
if str !in c.used_str {
|
||||
c.used_str << str
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Using pre-allocated array with capacity
|
||||
struct Context2 {
|
||||
mut:
|
||||
used_str []string
|
||||
}
|
||||
|
||||
fn (mut c Context2) add_used(str string) {
|
||||
if str !in c.used_str {
|
||||
c.used_str << str
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Using map
|
||||
struct Context3 {
|
||||
mut:
|
||||
used_str map[string]bool
|
||||
}
|
||||
|
||||
fn (mut c Context3) add_used(str string) {
|
||||
c.used_str[str] = true
|
||||
}
|
||||
|
||||
// Method 4: Using set
|
||||
struct Context4 {
|
||||
mut:
|
||||
used_str datatypes.Set[string]
|
||||
}
|
||||
|
||||
fn (mut c Context4) add_used(str string) {
|
||||
c.used_str.add(str)
|
||||
}
|
||||
|
||||
// Generate random test strings
|
||||
fn generate_test_strings(count int, duplicate_ratio f64) []string {
|
||||
mut strs := []string{cap: count}
|
||||
unique_count := int(f64(count) * (1.0 - duplicate_ratio))
|
||||
// First generate a batch of unique strings
|
||||
for i in 0 .. unique_count {
|
||||
strs << 'str_${i}_${time.ticks()}' // Add timestamp to reduce duplication rate
|
||||
}
|
||||
// The remaining part uses duplicate strings
|
||||
for i in 0 .. (count - unique_count) {
|
||||
strs << strs[i % unique_count] // Cycle through the first half of strings to create duplicates
|
||||
}
|
||||
return strs
|
||||
}
|
||||
|
||||
fn main() {
|
||||
num_strs := 10000 // Total number of strings
|
||||
duplicate_ratio := 0.3 // Duplicate string ratio (30%)
|
||||
test_strs := generate_test_strings(num_strs, duplicate_ratio)
|
||||
println('Generated test strings: ${test_strs.len} (approximately ${int(duplicate_ratio * 100)}% are duplicates)')
|
||||
|
||||
// Test method 1: basic array (no pre-allocation)
|
||||
mut ctx1 := Context1{}
|
||||
sw1 := time.new_stopwatch()
|
||||
for str in test_strs {
|
||||
ctx1.add_used(str)
|
||||
}
|
||||
time1 := sw1.elapsed().milliseconds()
|
||||
println('Method 1 (basic array) - Time: ${time1}ms, Final unique strings: ${ctx1.used_str.len}')
|
||||
|
||||
// Test method 2: pre-allocated array
|
||||
mut ctx2 := Context2{
|
||||
used_str: []string{cap: num_strs} // Pre-allocate capacity to avoid reallocations
|
||||
}
|
||||
sw2 := time.new_stopwatch()
|
||||
for str in test_strs {
|
||||
ctx2.add_used(str)
|
||||
}
|
||||
time2 := sw2.elapsed().milliseconds()
|
||||
println('Method 2 (pre-allocated array) - Time: ${time2}ms, Final unique strings: ${ctx2.used_str.len}')
|
||||
|
||||
// Test method 3: map
|
||||
mut ctx3 := Context3{}
|
||||
sw3 := time.new_stopwatch()
|
||||
for str in test_strs {
|
||||
ctx3.add_used(str)
|
||||
}
|
||||
time3 := sw3.elapsed().milliseconds()
|
||||
println('Method 3 (map) - Time: ${time3}ms, Final unique strings: ${ctx3.used_str.len}')
|
||||
|
||||
// Test method 4: set
|
||||
mut ctx4 := Context4{}
|
||||
sw4 := time.new_stopwatch()
|
||||
for str in test_strs {
|
||||
ctx4.add_used(str)
|
||||
}
|
||||
time4 := sw4.elapsed().milliseconds()
|
||||
println('Method 4 (set) - Time: ${time4}ms, Final unique strings: ${ctx4.used_str.size()}')
|
||||
|
||||
// Performance comparison
|
||||
println('\nPerformance comparison:')
|
||||
println('Method 2 (pre-allocated array) is ${f64(time1) / f64(time2):.2f} times faster than method 1 (basic array)')
|
||||
println('Method 3 (map) is ${f64(time1) / f64(time3):.2f} times faster than method 1 (basic array)')
|
||||
println('Method 4 (set) is ${f64(time1) / f64(time4):.2f} times faster than method 1 (basic array)')
|
||||
|
||||
if time3 < time4 {
|
||||
println('Map is slightly faster than set, difference: ${time4 - time3}ms')
|
||||
} else {
|
||||
println('Set is slightly faster than map, difference: ${time3 - time4}ms')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/obj_mac.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define ITERATIONS 1000
|
||||
#define MESSAGE "Benchmark message"
|
||||
|
||||
// Function to calculate the difference in microseconds between two timevals
|
||||
long time_diff_microseconds(struct timeval start, struct timeval end) {
|
||||
long seconds = end.tv_sec - start.tv_sec;
|
||||
long useconds = end.tv_usec - start.tv_usec;
|
||||
return seconds * 1000000 + useconds;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int iterations = ITERATIONS;
|
||||
struct timeval start, end;
|
||||
long total_gen_time = 0;
|
||||
long total_sign_time = 0;
|
||||
long total_verify_time = 0;
|
||||
long avg_gen_time, avg_sign_time, avg_verify_time;
|
||||
|
||||
// Benchmarking key generation
|
||||
printf("Benchmarking key generation...\n");
|
||||
for(int i = 0; i < iterations; i++) {
|
||||
gettimeofday(&start, NULL);
|
||||
|
||||
// Create a new EC_KEY object with the P-256 curve
|
||||
EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
||||
if (key == NULL) {
|
||||
fprintf(stderr, "Error creating EC_KEY object.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Generate the key
|
||||
if (EC_KEY_generate_key(key) != 1) {
|
||||
fprintf(stderr, "Error generating EC key.\n");
|
||||
EC_KEY_free(key);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
gettimeofday(&end, NULL);
|
||||
total_gen_time += time_diff_microseconds(start, end);
|
||||
|
||||
// Free the key
|
||||
EC_KEY_free(key);
|
||||
}
|
||||
avg_gen_time = total_gen_time / iterations;
|
||||
printf("Average key generation time: %ld µs\n", avg_gen_time);
|
||||
|
||||
// Generate a single key for signing and verification
|
||||
EC_KEY *priv_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
|
||||
if (priv_key == NULL) {
|
||||
fprintf(stderr, "Error creating EC_KEY object.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (EC_KEY_generate_key(priv_key) != 1) {
|
||||
fprintf(stderr, "Error generating EC key.\n");
|
||||
EC_KEY_free(priv_key);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Prepare the message hash
|
||||
unsigned char hash[SHA256_DIGEST_LENGTH];
|
||||
SHA256((unsigned char*)MESSAGE, strlen(MESSAGE), hash);
|
||||
|
||||
// Benchmarking signing
|
||||
printf("Benchmarking signing...\n");
|
||||
for(int i = 0; i < iterations; i++) {
|
||||
gettimeofday(&start, NULL);
|
||||
|
||||
ECDSA_SIG *signature = ECDSA_do_sign(hash, SHA256_DIGEST_LENGTH, priv_key);
|
||||
if (signature == NULL) {
|
||||
fprintf(stderr, "Error signing message.\n");
|
||||
EC_KEY_free(priv_key);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
gettimeofday(&end, NULL);
|
||||
total_sign_time += time_diff_microseconds(start, end);
|
||||
|
||||
// Free the signature
|
||||
ECDSA_SIG_free(signature);
|
||||
}
|
||||
avg_sign_time = total_sign_time / iterations;
|
||||
printf("Average sign time: %ld µs\n", avg_sign_time);
|
||||
|
||||
// Create a signature for verification benchmarking
|
||||
ECDSA_SIG *signature = ECDSA_do_sign(hash, SHA256_DIGEST_LENGTH, priv_key);
|
||||
if (signature == NULL) {
|
||||
fprintf(stderr, "Error signing message for verification.\n");
|
||||
EC_KEY_free(priv_key);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Benchmarking verification
|
||||
printf("Benchmarking verification...\n");
|
||||
for(int i = 0; i < iterations; i++) {
|
||||
gettimeofday(&start, NULL);
|
||||
|
||||
int verify_status = ECDSA_do_verify(hash, SHA256_DIGEST_LENGTH, signature, priv_key);
|
||||
|
||||
gettimeofday(&end, NULL);
|
||||
total_verify_time += time_diff_microseconds(start, end);
|
||||
|
||||
if (verify_status != 1) {
|
||||
fprintf(stderr, "Signature verification failed.\n");
|
||||
ECDSA_SIG_free(signature);
|
||||
EC_KEY_free(priv_key);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
avg_verify_time = total_verify_time / iterations;
|
||||
printf("Average verify time: %ld µs\n", avg_verify_time);
|
||||
|
||||
// Cleanup
|
||||
ECDSA_SIG_free(signature);
|
||||
EC_KEY_free(priv_key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
iterations := 1000
|
||||
|
||||
fmt.Println("Benchmarking key generation...")
|
||||
var totalGenTime int64
|
||||
for i := 0; i < iterations; i++ {
|
||||
start := time.Now()
|
||||
_, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
totalGenTime += time.Since(start).Microseconds()
|
||||
}
|
||||
avgGenTime := totalGenTime / int64(iterations)
|
||||
fmt.Printf("Average key generation time: %d µs\n", avgGenTime)
|
||||
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
message := []byte("Benchmark message")
|
||||
|
||||
fmt.Println("Benchmarking signing...")
|
||||
var totalSignTime int64
|
||||
for i := 0; i < iterations; i++ {
|
||||
start := time.Now()
|
||||
_, _, err := ecdsa.Sign(rand.Reader, privKey, message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
totalSignTime += time.Since(start).Microseconds()
|
||||
}
|
||||
avgSignTime := totalSignTime / int64(iterations)
|
||||
fmt.Printf("Average sign time: %d µs\n", avgSignTime)
|
||||
|
||||
r, s, err := ecdsa.Sign(rand.Reader, privKey, message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pubKey := &privKey.PublicKey
|
||||
|
||||
fmt.Println("Benchmarking verification...")
|
||||
var totalVerifyTime int64
|
||||
for i := 0; i < iterations; i++ {
|
||||
start := time.Now()
|
||||
ecdsa.Verify(pubKey, message, r, s)
|
||||
totalVerifyTime += time.Since(start).Microseconds()
|
||||
}
|
||||
avgVerifyTime := totalVerifyTime / int64(iterations)
|
||||
fmt.Printf("Average verify time: %d µs\n", avgVerifyTime)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import time
|
||||
import crypto.ecdsa
|
||||
|
||||
fn main() {
|
||||
iterations := 1000
|
||||
|
||||
println('Benchmarking key generation...')
|
||||
mut total_gen_time := i64(0)
|
||||
for _ in 0 .. iterations {
|
||||
sw := time.new_stopwatch()
|
||||
_, _ := ecdsa.generate_key() or { panic(err) }
|
||||
elapsed := sw.elapsed().microseconds()
|
||||
total_gen_time += elapsed
|
||||
}
|
||||
avg_gen_time := total_gen_time / iterations
|
||||
println('Average key generation time: ${avg_gen_time} µs')
|
||||
|
||||
pub_key, priv_key := ecdsa.generate_key() or { panic(err) }
|
||||
message := 'Benchmark message'.bytes()
|
||||
|
||||
println('Benchmarking signing...')
|
||||
mut total_sign_time := i64(0)
|
||||
for _ in 0 .. iterations {
|
||||
sw := time.new_stopwatch()
|
||||
_ := priv_key.sign(message) or { panic(err) }
|
||||
elapsed := sw.elapsed().microseconds()
|
||||
total_sign_time += elapsed
|
||||
}
|
||||
avg_sign_time := total_sign_time / iterations
|
||||
println('Average sign time: ${avg_sign_time} µs')
|
||||
|
||||
sig := priv_key.sign(message) or { panic(err) }
|
||||
|
||||
println('Benchmarking verification...')
|
||||
mut total_verify_time := i64(0)
|
||||
for _ in 0 .. iterations {
|
||||
sw := time.new_stopwatch()
|
||||
_ := pub_key.verify(message, sig) or { panic(err) }
|
||||
elapsed := sw.elapsed().microseconds()
|
||||
total_verify_time += elapsed
|
||||
}
|
||||
avg_verify_time := total_verify_time / iterations
|
||||
println('Average verify time: ${avg_verify_time} µs')
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
# Comparing C# and V Boids Algorithm Implementations
|
||||
|
||||
## Running the C# program:
|
||||
|
||||
```
|
||||
dotnet run
|
||||
```
|
||||
|
||||
Creating a release version of the C# program:
|
||||
|
||||
```
|
||||
dotnet publish -c Release -r ubuntu.20.04-x64
|
||||
```
|
||||
|
||||
The generated executable will be in
|
||||
`/v/vnew/bench/vectors/bin/Release/net7.0/ubuntu.20.04-x64/publish/vectors`
|
||||
Its size is ~64MB . After stripping, the executable shrinks to just 11MB,
|
||||
but unfortunately it also stops running after stripping :-| .
|
||||
|
||||
## Compiling and running the V program:
|
||||
|
||||
```
|
||||
v crun vectors.v
|
||||
```
|
||||
|
||||
... produces and runs a `vectors` executable which is ~1.3MB in size.
|
||||
|
||||
Compiling and running the V program, compiled with -prod:
|
||||
|
||||
```
|
||||
v -prod crun vectors.v
|
||||
```
|
||||
|
||||
... produces and runs a `vectors` executable which is ~176KB in size.
|
||||
After stripping, the executable shrinks to 157KB. It can still run after
|
||||
stripping.
|
||||
|
||||
Note: using `crun` will make sure that the compilation will happen just
|
||||
once at the start, and then the executable will be just reused by the
|
||||
subsequent commands with identical options. It will also ensure that
|
||||
the compiled executable will not be removed, unlike `run` .
|
||||
|
||||
## Some measurements and comparisons
|
||||
|
||||
Note: the following was done on Intel(R) Core(TM) i3-3225, 16GB RAM:
|
||||
|
||||
```
|
||||
#0 13:41:35 ᛋ master /v/vnew/bench/vectors❱rm -rf vectors
|
||||
#0 13:41:49 ᛋ master /v/vnew/bench/vectors❱
|
||||
#0 13:41:49 ᛋ master /v/vnew/bench/vectors❱v -o vectors_development vectors.v
|
||||
#0 13:42:14 ᛋ master /v/vnew/bench/vectors❱v -o vectors_production -prod vectors.v
|
||||
#0 13:42:28 ᛋ master /v/vnew/bench/vectors❱
|
||||
#0 13:42:29 ᛋ master /v/vnew/bench/vectors❱hyperfine ./bin/Release/net7.0/ubuntu.20.04-x64/publish/vectors ./vectors_development ./vectors_production
|
||||
Benchmark 1: ./bin/Release/net7.0/ubuntu.20.04-x64/publish/vectors
|
||||
Time (mean ± σ): 347.4 ms ± 7.4 ms [User: 334.4 ms, System: 13.0 ms]
|
||||
Range (min … max): 340.2 ms … 361.7 ms 10 runs
|
||||
|
||||
Benchmark 2: ./vectors_development
|
||||
Time (mean ± σ): 882.6 ms ± 14.0 ms [User: 880.3 ms, System: 2.3 ms]
|
||||
Range (min … max): 862.4 ms … 912.9 ms 10 runs
|
||||
|
||||
Benchmark 3: ./vectors_production
|
||||
Time (mean ± σ): 217.9 ms ± 9.4 ms [User: 216.8 ms, System: 0.9 ms]
|
||||
Range (min … max): 206.4 ms … 241.3 ms 12 runs
|
||||
|
||||
Summary
|
||||
./vectors_production ran
|
||||
1.59 ± 0.08 times faster than ./bin/Release/net7.0/ubuntu.20.04-x64/publish/vectors
|
||||
4.05 ± 0.19 times faster than ./vectors_development
|
||||
#0 13:43:00 ᛋ master /v/vnew/bench/vectors❱
|
||||
#0 13:45:07 ᛋ master /v/vnew/bench/vectors❱ls -nlarS ./bin/Release/net7.0/ubuntu.20.04-x64/publish/vectors ./vectors_development ./vectors_production
|
||||
-rwxrwxr-x 1 1000 1000 179384 Sep 6 13:42 ./vectors_production
|
||||
-rwxrwxr-x 1 1000 1000 1320764 Sep 6 13:42 ./vectors_development
|
||||
-rwxr-xr-x 1 1000 1000 66732821 Sep 6 13:40 ./bin/Release/net7.0/ubuntu.20.04-x64/publish/vectors
|
||||
#0 13:45:12 ᛋ master /v/vnew/bench/vectors❱
|
||||
#0 13:53:12 ᛋ master /v/vnew/bench/vectors❱alias xtime='/usr/bin/time -f "CPU: %Us\tReal: %es\tElapsed: %E\tRAM: %MKB\t%C"'
|
||||
#0 13:53:42 ᛋ master /v/vnew/bench/vectors❱xtime ./vectors_development
|
||||
5.0498380931718074e+07 - 5.0504723697762154e+07 - 5.040198063489048e+07
|
||||
0.0 - 0.0 - 0.0
|
||||
CPU: 0.87s Real: 0.87s Elapsed: 0:00.87 RAM: 4404KB ./vectors_development
|
||||
#0 13:53:52 ᛋ master /v/vnew/bench/vectors❱xtime ./vectors_production
|
||||
4.971971434731853e+07 - 4.973120986372047e+07 - 5.030988639116867e+07
|
||||
0.0 - 0.0 - 0.0
|
||||
CPU: 0.20s Real: 0.20s Elapsed: 0:00.20 RAM: 3228KB ./vectors_production
|
||||
#0 13:53:58 ᛋ master /v/vnew/bench/vectors❱xtime ./bin/Release/net7.0/ubuntu.20.04-x64/publish/vectors
|
||||
(49627678.97075553, 50302418.6269631, 49705582.70645027)
|
||||
(0, 0, 0)
|
||||
CPU: 0.33s Real: 0.34s Elapsed: 0:00.34 RAM: 30544KB ./bin/Release/net7.0/ubuntu.20.04-x64/publish/vectors
|
||||
#0 13:54:02 ᛋ master /v/vnew/bench/vectors❱
|
||||
#0 14:01:33 ᛋ master /v/vnew/bench/vectors❱
|
||||
#0 14:01:35 ᛋ master /v/vnew/bench/vectors❱xtime v vectors.v
|
||||
CPU: 0.41s Real: 0.36s Elapsed: 0:00.36 RAM: 59412KB v vectors.v
|
||||
#0 14:01:41 ᛋ master /v/vnew/bench/vectors❱
|
||||
#0 14:01:42 ᛋ master /v/vnew/bench/vectors❱xtime v -prod vectors.v
|
||||
CPU: 4.97s Real: 5.11s Elapsed: 0:05.11 RAM: 80732KB v -prod vectors.v
|
||||
#0 14:01:48 ᛋ master /v/vnew/bench/vectors❱
|
||||
#0 14:01:50 ᛋ master /v/vnew/bench/vectors❱xtime dotnet publish -c Release -r ubuntu.20.04-x64
|
||||
MSBuild version 17.7.1+971bf70db for .NET
|
||||
Determining projects to restore...
|
||||
All projects are up-to-date for restore.
|
||||
vectors -> /v/vnew/bench/vectors/bin/Release/net7.0/ubuntu.20.04-x64/vectors.dll
|
||||
vectors -> /v/vnew/bench/vectors/bin/Release/net7.0/ubuntu.20.04-x64/publish/
|
||||
CPU: 2.34s Real: 2.64s Elapsed: 0:02.64 RAM: 159816KB dotnet publish -c Release -r ubuntu.20.04-x64
|
||||
#0 14:01:56 ᛋ master /v/vnew/bench/vectors❱
|
||||
```
|
||||
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
|
||||
internal readonly struct Vector
|
||||
{
|
||||
public readonly double X;
|
||||
public readonly double Y;
|
||||
public readonly double Z;
|
||||
|
||||
public Vector(double x, double y, double z)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"({X}, {Y}, {Z})";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
const int boidsCount = 10000;
|
||||
|
||||
var positions = new Vector[boidsCount];
|
||||
var velocities = new Vector[boidsCount];
|
||||
|
||||
const double maxCoordinate = 10000.0;
|
||||
|
||||
var random = new Random();
|
||||
|
||||
for (var positionIndex = 0; positionIndex < positions.Length; positionIndex++)
|
||||
{
|
||||
positions[positionIndex] = new Vector(
|
||||
x: random.NextDouble() * maxCoordinate,
|
||||
y: random.NextDouble() * maxCoordinate,
|
||||
z: random.NextDouble() * maxCoordinate
|
||||
);
|
||||
}
|
||||
|
||||
const double cohesionDistance = 10.0;
|
||||
const double separationDistance = 5.0;
|
||||
|
||||
var closeBoids = new List<int>();
|
||||
|
||||
for (var boidIndex = 0; boidIndex < positions.Length; boidIndex++)
|
||||
{
|
||||
var position = positions[boidIndex];
|
||||
closeBoids.Clear();
|
||||
|
||||
for (var otherBoidIndex = 0; otherBoidIndex < positions.Length; otherBoidIndex++)
|
||||
{
|
||||
if (boidIndex == otherBoidIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var otherPosition = positions[otherBoidIndex];
|
||||
|
||||
var differenceX = position.X - otherPosition.X;
|
||||
var differenceY = position.Y - otherPosition.Y;
|
||||
var differenceZ = position.Z - otherPosition.Z;
|
||||
|
||||
var distance = differenceX * differenceX +
|
||||
differenceY * differenceY +
|
||||
differenceZ * differenceZ;
|
||||
|
||||
if (distance <= cohesionDistance * cohesionDistance)
|
||||
{
|
||||
closeBoids.Add(otherBoidIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (closeBoids.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var cohesion = new Vector(0.0, 0.0, 0.0);
|
||||
var separation = new Vector(0.0, 0.0, 0.0);
|
||||
var separationCount = 0;
|
||||
var alignment = new Vector(0.0, 0.0, 0.0);
|
||||
|
||||
foreach (var closeBoidIndex in closeBoids)
|
||||
{
|
||||
var closeBoidPosition = positions[closeBoidIndex];
|
||||
|
||||
cohesion = new Vector(
|
||||
cohesion.X + closeBoidPosition.X,
|
||||
cohesion.Y + closeBoidPosition.Y,
|
||||
cohesion.Z + closeBoidPosition.Z
|
||||
);
|
||||
|
||||
var differenceFromClosest = new Vector(
|
||||
position.X - closeBoidPosition.X,
|
||||
position.Y - closeBoidPosition.Y,
|
||||
position.Z - closeBoidPosition.Z
|
||||
);
|
||||
|
||||
var differenceMagnitude = Math.Sqrt(differenceFromClosest.X * differenceFromClosest.X + differenceFromClosest.Y * differenceFromClosest.Y + differenceFromClosest.Z * differenceFromClosest.Z);
|
||||
|
||||
if (differenceMagnitude <= separationDistance)
|
||||
{
|
||||
separation = new Vector(
|
||||
separation.X + differenceFromClosest.X / differenceMagnitude,
|
||||
separation.Y + differenceFromClosest.Y / differenceMagnitude,
|
||||
separation.Z + differenceFromClosest.Z / differenceMagnitude
|
||||
);
|
||||
|
||||
separationCount++;
|
||||
}
|
||||
|
||||
var closeBoidVelocity = velocities[closeBoidIndex];
|
||||
|
||||
alignment = new Vector(
|
||||
alignment.X + closeBoidVelocity.X,
|
||||
alignment.Y + closeBoidVelocity.Y,
|
||||
alignment.Z + closeBoidVelocity.Z
|
||||
);
|
||||
}
|
||||
|
||||
cohesion = new Vector(
|
||||
cohesion.X / closeBoids.Count,
|
||||
cohesion.Y / closeBoids.Count,
|
||||
cohesion.Z / closeBoids.Count
|
||||
);
|
||||
|
||||
var cohesionForce = new Vector(
|
||||
cohesion.X - position.X,
|
||||
cohesion.Y - position.Y,
|
||||
cohesion.Z - position.Z
|
||||
);
|
||||
|
||||
if (separationCount > 0)
|
||||
{
|
||||
separation = new Vector(
|
||||
separation.X / separationCount,
|
||||
separation.Y / separationCount,
|
||||
separation.Z / separationCount
|
||||
);
|
||||
}
|
||||
|
||||
alignment = new Vector(
|
||||
alignment.X / closeBoids.Count,
|
||||
alignment.Y / closeBoids.Count,
|
||||
alignment.Z / closeBoids.Count
|
||||
);
|
||||
|
||||
var currentVelocity = velocities[boidIndex];
|
||||
|
||||
velocities[boidIndex] = new Vector(
|
||||
currentVelocity.X + cohesionForce.X + separation.X + alignment.X,
|
||||
currentVelocity.Y + cohesionForce.Y + separation.Y + alignment.Y,
|
||||
currentVelocity.Z + cohesionForce.Z + separation.Z + alignment.Z
|
||||
);
|
||||
}
|
||||
|
||||
var positionSum = new Vector(0.0, 0.0, 0.0);
|
||||
var velocitySum = new Vector(0.0, 0.0, 0.0);
|
||||
|
||||
for (var boidIndex = 0; boidIndex < positions.Length; boidIndex++)
|
||||
{
|
||||
var position = positions[boidIndex];
|
||||
var velocity = velocities[boidIndex];
|
||||
|
||||
positions[boidIndex] = new Vector(
|
||||
position.X + velocity.X,
|
||||
position.Y + velocity.Y,
|
||||
position.Z + velocity.Z
|
||||
);
|
||||
|
||||
positionSum = new Vector(
|
||||
positionSum.X + position.X,
|
||||
positionSum.Y + position.Y,
|
||||
positionSum.Z + position.Z
|
||||
);
|
||||
|
||||
velocitySum = new Vector(
|
||||
velocitySum.X + velocity.X,
|
||||
velocitySum.Y + velocity.Y,
|
||||
velocitySum.Z + velocity.Z
|
||||
);
|
||||
}
|
||||
|
||||
Console.WriteLine(positionSum.ToString());
|
||||
Console.WriteLine(velocitySum.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
{"runtimeOptions":{"framework":{"name":"Microsoft.NETCore.App","version":"7.0.10"}}}
|
||||
@@ -0,0 +1,164 @@
|
||||
module main
|
||||
|
||||
import rand
|
||||
import math
|
||||
|
||||
struct Vector {
|
||||
x f64
|
||||
y f64
|
||||
z f64
|
||||
}
|
||||
|
||||
const boids_count = 10000
|
||||
const max_coordinate = 10000.0
|
||||
const cohesion_distance = 10.0
|
||||
const separation_distance = 5.0
|
||||
|
||||
@[direct_array_access]
|
||||
fn main() {
|
||||
mut positions := [boids_count]Vector{}
|
||||
mut velocities := [boids_count]Vector{}
|
||||
|
||||
for position_index in 0 .. positions.len {
|
||||
positions[position_index] = Vector{
|
||||
x: rand.f64() * max_coordinate
|
||||
y: rand.f64() * max_coordinate
|
||||
z: rand.f64() * max_coordinate
|
||||
}
|
||||
}
|
||||
|
||||
for boid_index in 0 .. positions.len {
|
||||
position := positions[boid_index]
|
||||
mut close_boids_ids := []int{}
|
||||
|
||||
for other_boid_index in 0 .. positions.len {
|
||||
if boid_index == other_boid_index {
|
||||
continue
|
||||
}
|
||||
|
||||
other_position := positions[other_boid_index]
|
||||
|
||||
difference_x := position.x - other_position.x
|
||||
difference_y := position.y - other_position.y
|
||||
difference_z := position.z - other_position.z
|
||||
|
||||
distance := difference_x * difference_x + difference_y * difference_y +
|
||||
difference_z * difference_z
|
||||
|
||||
if distance <= cohesion_distance * cohesion_distance {
|
||||
close_boids_ids << other_boid_index
|
||||
}
|
||||
}
|
||||
|
||||
if close_boids_ids.len == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
mut cohesion := Vector{}
|
||||
mut separation := Vector{}
|
||||
mut separation_count := 0
|
||||
mut alignment := Vector{}
|
||||
|
||||
for close_boid_id in close_boids_ids {
|
||||
close_boid_position := positions[close_boid_id]
|
||||
|
||||
cohesion = Vector{
|
||||
x: cohesion.x + close_boid_position.x
|
||||
y: cohesion.y + close_boid_position.y
|
||||
z: cohesion.z + close_boid_position.z
|
||||
}
|
||||
|
||||
difference_from_closest := Vector{
|
||||
x: position.x - close_boid_position.x
|
||||
y: position.y - close_boid_position.y
|
||||
z: position.z - close_boid_position.z
|
||||
}
|
||||
|
||||
difference_magnitude := math.sqrt(
|
||||
difference_from_closest.x * difference_from_closest.x +
|
||||
difference_from_closest.y * difference_from_closest.y +
|
||||
difference_from_closest.z * difference_from_closest.z)
|
||||
|
||||
if difference_magnitude <= separation_distance {
|
||||
separation = Vector{
|
||||
x: separation.x + difference_from_closest.x / difference_magnitude
|
||||
y: separation.y + difference_from_closest.y / difference_magnitude
|
||||
z: separation.z + difference_from_closest.z / difference_magnitude
|
||||
}
|
||||
|
||||
separation_count += 1
|
||||
}
|
||||
|
||||
close_boid_velocity := velocities[close_boid_id]
|
||||
|
||||
alignment = Vector{
|
||||
x: alignment.x + close_boid_velocity.x
|
||||
y: alignment.y + close_boid_velocity.y
|
||||
z: alignment.z + close_boid_velocity.z
|
||||
}
|
||||
}
|
||||
|
||||
cohesion = Vector{
|
||||
x: cohesion.x / close_boids_ids.len
|
||||
y: cohesion.y / close_boids_ids.len
|
||||
z: cohesion.z / close_boids_ids.len
|
||||
}
|
||||
|
||||
cohesion_force := Vector{
|
||||
x: cohesion.x - position.x
|
||||
y: cohesion.y - position.y
|
||||
z: cohesion.z - position.z
|
||||
}
|
||||
|
||||
if separation_count > 0 {
|
||||
separation = Vector{
|
||||
x: separation.x / separation_count
|
||||
y: separation.y / separation_count
|
||||
z: separation.z / separation_count
|
||||
}
|
||||
}
|
||||
|
||||
alignment = Vector{
|
||||
x: alignment.x / close_boids_ids.len
|
||||
y: alignment.y / close_boids_ids.len
|
||||
z: alignment.z / close_boids_ids.len
|
||||
}
|
||||
|
||||
current_velocity := velocities[boid_index]
|
||||
|
||||
velocities[boid_index] = Vector{
|
||||
x: current_velocity.x + cohesion_force.x + separation.x + alignment.x
|
||||
y: current_velocity.y + cohesion_force.y + separation.y + alignment.y
|
||||
z: current_velocity.z + cohesion_force.z + separation.z + alignment.z
|
||||
}
|
||||
}
|
||||
|
||||
mut position_sum := Vector{}
|
||||
mut velocity_sum := Vector{}
|
||||
|
||||
for boid_index in 0 .. positions.len {
|
||||
position := positions[boid_index]
|
||||
velocity := velocities[boid_index]
|
||||
|
||||
positions[boid_index] = Vector{
|
||||
x: position.x + velocity.x
|
||||
y: position.y + velocity.y
|
||||
z: position.z + velocity.z
|
||||
}
|
||||
|
||||
position_sum = Vector{
|
||||
x: position_sum.x + position.x
|
||||
y: position_sum.y + position.y
|
||||
z: position_sum.z + position.z
|
||||
}
|
||||
|
||||
velocity_sum = Vector{
|
||||
x: velocity_sum.x + velocity.x
|
||||
y: velocity_sum.y + velocity.y
|
||||
z: velocity_sum.z + velocity.z
|
||||
}
|
||||
}
|
||||
|
||||
println('${position_sum.x} - ${position_sum.y} - ${position_sum.z}')
|
||||
println('${velocity_sum.x} - ${velocity_sum.y} - ${velocity_sum.z}')
|
||||
}
|
||||
Reference in new issue
Block a user