diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 27ed55ab0c..a1d2cf2a33 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -124,27 +124,27 @@ jobs:
run: |
thirdparty/tcc/tcc.exe -version
./v -cg -o v cmd/v # Make sure vtcc can build itself twice
- - name: v self compilation with -gc boehm
+ - name: v self compilation with -gc boehm_full_opt
run: |
- ./v -gc boehm -o v2 cmd/v && ./v2 -gc boehm -o v3 cmd/v && ./v3 -gc boehm -o v4 cmd/v
+ ./v -gc boehm_full_opt -o v2 cmd/v && ./v2 -gc boehm_full_opt -o v3 cmd/v && ./v3 -gc boehm_full_opt -o v4 cmd/v
mv v4 v
- name: v doctor
run: |
./v doctor
- - name: Verify `v -gc boehm test` works
+ - name: Verify `v -gc boehm_full_opt test` works
run: |
- ./v -gc boehm cmd/tools/test_if_v_test_system_works.v
+ ./v -gc boehm_full_opt cmd/tools/test_if_v_test_system_works.v
./cmd/tools/test_if_v_test_system_works
- - name: Self tests with `-gc boehm` with V compiler using Boehm-GC itself
- run: ./v -gc boehm -silent test-self
+ - name: Self tests with `-gc boehm_full_opt` with V compiler using Boehm-GC itself
+ run: ./v -gc boehm_full_opt -silent test-self
- name: Test leak detector
run: |
./v -gc boehm_leak -o testcase_leak vlib/v/tests/testcase_leak.v
./testcase_leak 2>leaks.txt
grep "Found 1 leaked object" leaks.txt && grep ", sz=1000," leaks.txt
- - name: Test leak detector not being active for `-gc boehm`
+ - name: Test leak detector not being active for `-gc boehm_full_opt`
run: |
- ./v -gc boehm -o testcase_leak vlib/v/tests/testcase_leak.v
+ ./v -gc boehm_full_opt -o testcase_leak vlib/v/tests/testcase_leak.v
./testcase_leak 2>leaks.txt
[ "$(stat -c %s leaks.txt)" = "0" ]
- name: Test leak detector not being active for normal compile
diff --git a/cmd/v/help/build-c.txt b/cmd/v/help/build-c.txt
index eae53c59f2..d6890e6cbe 100644
--- a/cmd/v/help/build-c.txt
+++ b/cmd/v/help/build-c.txt
@@ -74,10 +74,12 @@ see also `v help build`.
Use and link an optional garbage collector. Only tThe Boehm–Demers–Weiser
garbage collector is supported currently with the following sub-options:
- `-gc boehm` ........ selects the default mode for the architecture
- `-gc boehm_full` ... full garbage collection mode
- `-gc boehm_incr` ... incremental/generational garbage collection mode
- `-gc boehm_leak` ... leak detection mode
+ `-gc boehm` ........... selects the default mode for the architecture
+ `-gc boehm_full` ...... full garbage collection mode
+ `-gc boehm_incr` ...... incremental/generational garbage collection mode
+ `-gc boehm_full_opt` .. optimized full garbage collection mode
+ `-gc boehm_incr_opt` .. optimized incremental/generational garbage collection mode
+ `-gc boehm_leak` ...... leak detection mode
You need to install a `libgc-dev` package first, or install it manually from:
diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v
index d40cfa2dd1..0ade62c176 100644
--- a/vlib/builtin/array.v
+++ b/vlib/builtin/array.v
@@ -96,7 +96,7 @@ fn (mut a array) ensure_cap(required int) {
}
new_size := cap * a.element_size
mut new_data := &byte(0)
- if a.cap > 0 {
+ if a.data != voidptr(0) {
new_data = unsafe { realloc_data(a.data, a.cap * a.element_size, new_size) }
} else {
new_data = vcalloc(new_size)
@@ -656,3 +656,167 @@ pub fn (data voidptr) vbytes(len int) []byte {
pub fn (data &byte) vbytes(len int) []byte {
return unsafe { voidptr(data).vbytes(len) }
}
+
+// non-pub "noscan" versions of some above functions
+fn __new_array_noscan(mylen int, cap int, elm_size int) array {
+ cap_ := if cap < mylen { mylen } else { cap }
+ arr := array{
+ element_size: elm_size
+ data: vcalloc_noscan(cap_ * elm_size)
+ len: mylen
+ cap: cap_
+ }
+ return arr
+}
+
+fn __new_array_with_default_noscan(mylen int, cap int, elm_size int, val voidptr) array {
+ cap_ := if cap < mylen { mylen } else { cap }
+ mut arr := array{
+ element_size: elm_size
+ data: vcalloc_noscan(cap_ * elm_size)
+ len: mylen
+ cap: cap_
+ }
+ if val != 0 {
+ for i in 0 .. arr.len {
+ unsafe { arr.set_unsafe(i, val) }
+ }
+ }
+ return arr
+}
+
+fn __new_array_with_array_default_noscan(mylen int, cap int, elm_size int, val array) array {
+ cap_ := if cap < mylen { mylen } else { cap }
+ mut arr := array{
+ element_size: elm_size
+ data: vcalloc_noscan(cap_ * elm_size)
+ len: mylen
+ cap: cap_
+ }
+ for i in 0 .. arr.len {
+ val_clone := val.clone()
+ unsafe { arr.set_unsafe(i, &val_clone) }
+ }
+ return arr
+}
+
+// Private function, used by V (`nums := [1, 2, 3]`)
+fn new_array_from_c_array_noscan(len int, cap int, elm_size int, c_array voidptr) array {
+ cap_ := if cap < len { len } else { cap }
+ arr := array{
+ element_size: elm_size
+ data: vcalloc_noscan(cap_ * elm_size)
+ len: len
+ cap: cap_
+ }
+ // TODO Write all memory functions (like memcpy) in V
+ unsafe { C.memcpy(arr.data, c_array, len * elm_size) }
+ return arr
+}
+
+fn (a array) repeat_noscan(count int) array {
+ if count < 0 {
+ panic('array.repeat: count is negative: $count')
+ }
+ mut size := count * a.len * a.element_size
+ if size == 0 {
+ size = a.element_size
+ }
+ arr := array{
+ element_size: a.element_size
+ data: vcalloc_noscan(size)
+ len: count * a.len
+ cap: count * a.len
+ }
+ size_of_array := int(sizeof(array))
+ for i in 0 .. count {
+ if a.len > 0 && a.element_size == size_of_array {
+ ary := array{}
+ unsafe { C.memcpy(&ary, a.data, size_of_array) }
+ ary_clone := ary.clone()
+ unsafe { C.memcpy(arr.get_unsafe(i * a.len), &ary_clone, a.len * a.element_size) }
+ } else {
+ unsafe { C.memcpy(arr.get_unsafe(i * a.len), &byte(a.data), a.len * a.element_size) }
+ }
+ }
+ return arr
+}
+
+pub fn (a &array) clone_noscan() array {
+ mut size := a.cap * a.element_size
+ if size == 0 {
+ size++
+ }
+ mut arr := array{
+ element_size: a.element_size
+ data: vcalloc_noscan(size)
+ len: a.len
+ cap: a.cap
+ }
+ // Recursively clone-generated elements if array element is array type
+ size_of_array := int(sizeof(array))
+ if a.element_size == size_of_array {
+ mut is_elem_array := true
+ for i in 0 .. a.len {
+ ar := array{}
+ unsafe { C.memcpy(&ar, a.get_unsafe(i), size_of_array) }
+ if ar.len > ar.cap || ar.cap <= 0 || ar.element_size <= 0 {
+ is_elem_array = false
+ break
+ }
+ ar_clone := ar.clone()
+ unsafe { arr.set_unsafe(i, &ar_clone) }
+ }
+ if is_elem_array {
+ return arr
+ }
+ }
+
+ if !isnil(a.data) {
+ unsafe { C.memcpy(&byte(arr.data), a.data, a.cap * a.element_size) }
+ }
+ return arr
+}
+
+fn (a &array) slice_clone_noscan(start int, _end int) array {
+ mut end := _end
+ $if !no_bounds_checking ? {
+ if start > end {
+ panic('array.slice: invalid slice index ($start > $end)')
+ }
+ if end > a.len {
+ panic('array.slice: slice bounds out of range ($end >= $a.len)')
+ }
+ if start < 0 {
+ panic('array.slice: slice bounds out of range ($start < 0)')
+ }
+ }
+ mut data := &byte(0)
+ unsafe {
+ data = &byte(a.data) + start * a.element_size
+ }
+ l := end - start
+ res := array{
+ element_size: a.element_size
+ data: data
+ len: l
+ cap: l
+ }
+ return res.clone_noscan()
+}
+
+fn (a array) reverse_noscan() array {
+ if a.len < 2 {
+ return a
+ }
+ mut arr := array{
+ element_size: a.element_size
+ data: vcalloc_noscan(a.cap * a.element_size)
+ len: a.len
+ cap: a.cap
+ }
+ for i in 0 .. a.len {
+ unsafe { arr.set_unsafe(i, a.get_unsafe(a.len - 1 - i)) }
+ }
+ return arr
+}
diff --git a/vlib/builtin/builtin.c.v b/vlib/builtin/builtin.c.v
index 224afc275f..40f29c3421 100644
--- a/vlib/builtin/builtin.c.v
+++ b/vlib/builtin/builtin.c.v
@@ -302,7 +302,7 @@ pub fn realloc_data(old_data &byte, old_size int, new_size int) &byte {
// Unlike `v_calloc` vcalloc checks for negative values given in `n`.
pub fn vcalloc(n int) &byte {
if n < 0 {
- panic('calloc(<=0)')
+ panic('calloc(<0)')
} else if n == 0 {
return &byte(0)
}
@@ -313,6 +313,24 @@ pub fn vcalloc(n int) &byte {
}
}
+// special versions of the above that allocate memory which is not scanned
+// for pointers (but is collected) when the Boehm garbage collection is used
+pub fn vcalloc_noscan(n int) &byte {
+ $if gcboehm ? {
+ $if vplayground ? {
+ if n > 10000 {
+ panic('allocating more than 10 KB is not allowed in the playground')
+ }
+ }
+ if n < 0 {
+ panic('calloc(<0)')
+ }
+ return &byte(unsafe { C.memset(C.GC_MALLOC_ATOMIC(n), 0, n) })
+ } $else {
+ return unsafe { vcalloc(n) }
+ }
+}
+
// free allows for manually freeing memory allocated at the address `ptr`.
[unsafe]
pub fn free(ptr voidptr) {
diff --git a/vlib/builtin/builtin_d_gcboehm.v b/vlib/builtin/builtin_d_gcboehm.v
index 3e6f518638..f773c3ca8b 100644
--- a/vlib/builtin/builtin_d_gcboehm.v
+++ b/vlib/builtin/builtin_d_gcboehm.v
@@ -34,6 +34,8 @@ $if gcboehm_leak ? {
// compiled with `-gc boehm` or `-gc boehm_leak`.
fn C.GC_MALLOC(n size_t) voidptr
+fn C.GC_MALLOC_ATOMIC(n size_t) voidptr
+
fn C.GC_MALLOC_UNCOLLECTABLE(n size_t) voidptr
fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr
diff --git a/vlib/builtin/builtin_notd_gcboehm.v b/vlib/builtin/builtin_notd_gcboehm.v
index a3aae70721..44790729b5 100644
--- a/vlib/builtin/builtin_notd_gcboehm.v
+++ b/vlib/builtin/builtin_notd_gcboehm.v
@@ -6,6 +6,8 @@ module builtin
fn C.GC_MALLOC(n size_t) voidptr
+fn C.GC_MALLOC_ATOMIC(n size_t) voidptr
+
fn C.GC_MALLOC_UNCOLLECTABLE(n size_t) voidptr
fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr
diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v
index 082b1713fd..3d853075b3 100644
--- a/vlib/v/gen/c/array.v
+++ b/vlib/v/gen/c/array.v
@@ -50,10 +50,11 @@ fn (mut g Gen) array_init(node ast.ArrayInit) {
if node.exprs.len == 0 {
elem_sym := g.table.get_type_symbol(node.elem_type)
is_default_array := elem_sym.kind == .array && node.has_default
+ noscan := g.check_noscan(node.elem_type)
if is_default_array {
- g.write('__new_array_with_array_default(')
+ g.write('__new_array_with_array_default${noscan}(')
} else {
- g.write('__new_array_with_default(')
+ g.write('__new_array_with_default${noscan}(')
}
if node.has_len {
g.expr(node.len_expr)
@@ -145,7 +146,8 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
g.expr(node.left)
g.writeln(';')
g.writeln('int ${tmp}_len = ${tmp}_orig.len;')
- g.writeln('$ret_typ $tmp = __new_array(0, ${tmp}_len, sizeof($ret_elem_type));\n')
+ noscan := g.check_noscan(ret_info.elem_type)
+ g.writeln('$ret_typ $tmp = __new_array${noscan}(0, ${tmp}_len, sizeof($ret_elem_type));\n')
i := g.new_tmp_var()
g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {')
g.writeln('\t$inp_elem_type it = (($inp_elem_type*) ${tmp}_orig.data)[$i];')
@@ -337,7 +339,8 @@ fn (mut g Gen) gen_array_filter(node ast.CallExpr) {
g.expr(node.left)
g.writeln(';')
g.writeln('int ${tmp}_len = ${tmp}_orig.len;')
- g.writeln('$styp $tmp = __new_array(0, ${tmp}_len, sizeof($elem_type_str));\n')
+ noscan := g.check_noscan(info.elem_type)
+ g.writeln('$styp $tmp = __new_array${noscan}(0, ${tmp}_len, sizeof($elem_type_str));\n')
i := g.new_tmp_var()
g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {')
g.writeln('\t$elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];')
diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v
index 51006b418a..72b6bd2f3c 100644
--- a/vlib/v/gen/c/cgen.v
+++ b/vlib/v/gen/c/cgen.v
@@ -427,7 +427,9 @@ pub fn (mut g Gen) init() {
}
g.comptime_defines.writeln('')
}
- if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm, .boehm_leak] {
+ if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm,
+ .boehm_leak,
+ ] {
g.comptime_defines.writeln('#define _VGCBOEHM (1)')
}
if g.pref.is_debug || 'debug' in g.pref.compile_defines {
@@ -5766,7 +5768,8 @@ fn (mut g Gen) type_default(typ_ ast.Type) string {
if elem_type_str.starts_with('C__') {
elem_type_str = elem_type_str[3..]
}
- init_str := '__new_array(0, 1, sizeof($elem_type_str))'
+ noscan := g.check_noscan(elem_typ)
+ init_str := '__new_array${noscan}(0, 1, sizeof($elem_type_str))'
if typ.has_flag(.shared_f) {
atyp := '__shared__Array_${g.table.get_type_symbol(elem_typ).cname}'
return '($atyp*)__dup_shared_array(&($atyp){.val = $init_str}, sizeof($atyp))'
@@ -6378,3 +6381,64 @@ fn (mut g Gen) trace(fbase string, message string) {
println('> g.trace | ${fbase:-10s} | $message')
}
}
+
+// returns true if `t` includes any pointer(s) - during garbage collection heap regions
+// that contain no pointers do not have to be scanned
+pub fn (mut g Gen) contains_ptr(el_typ ast.Type) bool {
+ if el_typ.is_ptr() || el_typ.is_pointer() {
+ return true
+ }
+ typ := g.unwrap_generic(el_typ)
+ if typ.is_ptr() {
+ return true
+ }
+ sym := g.table.get_final_type_symbol(typ)
+ if sym.language != .v {
+ return true
+ }
+ match sym.kind {
+ .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .char, .size_t, .rune, .bool,
+ .enum_ {
+ return false
+ }
+ .array_fixed {
+ info := sym.info as ast.ArrayFixed
+ return g.contains_ptr(info.elem_type)
+ }
+ .struct_ {
+ info := sym.info as ast.Struct
+ for embed in info.embeds {
+ if g.contains_ptr(embed) {
+ return true
+ }
+ }
+ for field in info.fields {
+ if g.contains_ptr(field.typ) {
+ return true
+ }
+ }
+ return false
+ }
+ .aggregate {
+ info := sym.info as ast.Aggregate
+ for atyp in info.types {
+ if g.contains_ptr(atyp) {
+ return true
+ }
+ }
+ return false
+ }
+ else {
+ return true
+ }
+ }
+}
+
+fn (mut g Gen) check_noscan(elem_typ ast.Type) string {
+ if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] {
+ if !g.contains_ptr(elem_typ) {
+ return '_noscan'
+ }
+ }
+ return ''
+}
diff --git a/vlib/v/gen/c/cmain.v b/vlib/v/gen/c/cmain.v
index 9cfab64545..bc093104d0 100644
--- a/vlib/v/gen/c/cmain.v
+++ b/vlib/v/gen/c/cmain.v
@@ -70,13 +70,15 @@ fn (mut g Gen) gen_c_main_function_header() {
fn (mut g Gen) gen_c_main_header() {
g.gen_c_main_function_header()
- if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm, .boehm_leak] {
+ if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm,
+ .boehm_leak,
+ ] {
g.writeln('#if defined(_VGCBOEHM)')
if g.pref.gc_mode == .boehm_leak {
g.writeln('\tGC_set_find_leak(1);')
}
g.writeln('\tGC_INIT();')
- if g.pref.gc_mode == .boehm_incr {
+ if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
g.writeln('\tGC_enable_incremental();')
}
g.writeln('#endif')
@@ -164,13 +166,15 @@ pub fn (mut g Gen) gen_c_main_for_tests() {
main_fn_start_pos := g.out.len
g.writeln('')
g.gen_c_main_function_header()
- if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm, .boehm_leak] {
+ if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm,
+ .boehm_leak,
+ ] {
g.writeln('#if defined(_VGCBOEHM)')
if g.pref.gc_mode == .boehm_leak {
g.writeln('\tGC_set_find_leak(1);')
}
g.writeln('\tGC_INIT();')
- if g.pref.gc_mode == .boehm_incr {
+ if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] {
g.writeln('\tGC_enable_incremental();')
}
g.writeln('#endif')
diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v
index 21e67e8175..4677fc0902 100644
--- a/vlib/v/gen/c/fn.v
+++ b/vlib/v/gen/c/fn.v
@@ -416,7 +416,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
g.inside_call = false
}
gen_keep_alive := node.is_keep_alive && node.return_type != ast.void_type
- && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm]
+ && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm]
gen_or := node.or_block.kind != .absent // && !g.is_autofree
is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result
cur_line := if is_gen_or_and_assign_rhs || gen_keep_alive { // && !g.is_autofree {
@@ -890,7 +890,8 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
if g.is_json_fn {
g.write(json_obj)
} else {
- if node.is_keep_alive && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm] {
+ if node.is_keep_alive
+ && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm] {
cur_line := g.go_before_stmt(0)
tmp_cnt_save = g.keep_alive_call_pregen(node)
g.write(cur_line)
diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v
index 9adc9bd5f1..2f3ee7a8a4 100644
--- a/vlib/v/gen/c/json.v
+++ b/vlib/v/gen/c/json.v
@@ -222,11 +222,12 @@ fn (mut g Gen) decode_array(value_type ast.Type) string {
$styp val = *($styp*)val2.data;
'
}
+ noscan := g.check_noscan(value_type)
return '
if(root && !cJSON_IsArray(root) && !cJSON_IsNull(root)) {
return (Option_Array_$styp){.state = 2, .err = v_error(string_add(_SLIT("Json element is not an array: "), tos2((byteptr)cJSON_PrintUnformatted(root))))};
}
- res = __new_array(0, 0, sizeof($styp));
+ res = __new_array${noscan}(0, 0, sizeof($styp));
const cJSON *jsval = NULL;
cJSON_ArrayForEach(jsval, root)
{
diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v
index 5d583ebb85..749b009483 100644
--- a/vlib/v/markused/markused.v
+++ b/vlib/v/markused/markused.v
@@ -100,6 +100,14 @@ pub fn mark_used(mut table ast.Table, pref &pref.Preferences, ast_files []ast.Fi
'os.init_os_args',
'os.init_os_args_wide',
]
+ if pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] {
+ all_fn_root_names << [
+ '__new_array_noscan',
+ '__new_array_with_default_noscan',
+ '__new_array_with_array_default_noscan',
+ 'new_array_from_c_array_noscan',
+ ]
+ }
for k, mut mfn in all_fns {
mut method_receiver_typename := ''
diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v
index 4e56cb19dd..98addd7776 100644
--- a/vlib/v/pref/pref.v
+++ b/vlib/v/pref/pref.v
@@ -21,6 +21,8 @@ pub enum GarbageCollectionMode {
no_gc
boehm_full // full garbage collection mode
boehm_incr // incremental garbage colletion mode
+ boehm_full_opt // full garbage collection mode
+ boehm_incr_opt // incremental garbage colletion mode
boehm // default Boehm-GC mode for architecture
boehm_leak // leak detection mode (makes `gc_check_leaks()` work)
}
@@ -241,6 +243,18 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
parse_define(mut res, 'gcboehm')
parse_define(mut res, 'gcboehm_incr')
}
+ 'boehm_full_opt' {
+ res.gc_mode = .boehm_full_opt
+ parse_define(mut res, 'gcboehm')
+ parse_define(mut res, 'gcboehm_full')
+ parse_define(mut res, 'gcboehm_opt')
+ }
+ 'boehm_incr_opt' {
+ res.gc_mode = .boehm_incr_opt
+ parse_define(mut res, 'gcboehm')
+ parse_define(mut res, 'gcboehm_incr')
+ parse_define(mut res, 'gcboehm_opt')
+ }
'boehm' {
res.gc_mode = .boehm
parse_define(mut res, 'gcboehm')
@@ -251,7 +265,13 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
parse_define(mut res, 'gcboehm_leak')
}
else {
- eprintln('unknown garbage collection mode, only `-gc boehm`, `-gc boehm_incr`, `-gc boehm_full` and `-gc boehm_leak` are supported')
+ eprintln('unknown garbage collection mode `-gc $gc_mode`, supported modes are:`')
+ eprintln(' `-gc boehm` ............ default mode for the platform')
+ eprintln(' `-gc boehm_full` ....... classic full collection')
+ eprintln(' `-gc boehm_incr` ....... incremental collection')
+ eprintln(' `-gc boehm_full_opt` ... optimized classic full collection')
+ eprintln(' `-gc boehm_incr_opt` ... optimized incremental collection')
+ eprintln(' `-gc boehm_leak` ....... leak detection (for debugging)')
exit(1)
}
}
diff --git a/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.pdf b/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.pdf
index e5d55d6121..b25c3e6d92 100644
Binary files a/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.pdf and b/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.pdf differ
diff --git a/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.svg b/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.svg
new file mode 100644
index 0000000000..ab7fedabdd
--- /dev/null
+++ b/vlib/v/tests/bench/gcboehm/GC_Ryzen_3800X_Linux.svg
@@ -0,0 +1,10619 @@
+
+
+
diff --git a/vlib/v/tests/bench/gcboehm/GC_bench.plt b/vlib/v/tests/bench/gcboehm/GC_bench.plt
index 0815a6309b..af99eaeec5 100644
--- a/vlib/v/tests/bench/gcboehm/GC_bench.plt
+++ b/vlib/v/tests/bench/gcboehm/GC_bench.plt
@@ -1,9 +1,17 @@
#!/usr/bin/gnuplot -persist
-set title "Boehm-GC: Full vs. Incremental/Generational Mode"
-set xlabel "Interval #"
+set title "Boehm-GC: Pause Times - Classic vs. Optimized Modes" font ",18"
+set xlabel "Interval #"
+set xtics out nomirror
+set xtic 1000000
+set grid noxtics ytics
set ylabel "Pause Time [ms]"
-set terminal pdfcairo transparent enhanced fontscale 0.5 size 5.00in, 3.00in
+set terminal pdfcairo transparent enhanced fontscale 0.5 size 5.00in, 3.00in
+set key box at 4810000,77 Left enhanced opaque samplen 3 height 0.5
set output "GC_bench.pdf"
-plot "boehm_full.txt" title "full GC" w i, "boehm_incr.txt" title "incr/generational GC" w i
+plot "boehm_full.txt" title "{/Monospace -gc boehm\\_full}" w i lt 1, "boehm_incr_opt.txt" title "{/Monospace -gc boehm\\_incr\\_opt}" w i lt 2, "boehm_full_opt.txt" title "{/Monospace -gc boehm\\_full\\_opt}" w i lt 7
+set output
+set terminal svg size 900,600 dynamic enhanced
+set output "GC_bench.svg"
+replot
set output
# EOF
diff --git a/vlib/v/tests/bench/gcboehm/GC_bench.v b/vlib/v/tests/bench/gcboehm/GC_bench.v
index ac32b6211e..411b122392 100644
--- a/vlib/v/tests/bench/gcboehm/GC_bench.v
+++ b/vlib/v/tests/bench/gcboehm/GC_bench.v
@@ -1,32 +1,43 @@
+import os
import time
import rand
import math
-struct MemObj {
+struct DataObj {
mut:
- nxt []&MemObj
+ data []f64
+}
+
+struct PtrObj {
+mut:
+ nxt []&DataObj
+}
+
+struct PtrPtrObj {
+mut:
+ nxt []&PtrObj
}
const (
- log2n = 10
+ log2n = 11
n = 1 << log2n
n4 = f64(u64(1) << (4 * log2n))
)
fn waste_mem() {
- mut objs := MemObj{
- nxt: []&MemObj{len: n}
+ mut objs := PtrPtrObj{
+ nxt: []&PtrObj{len: n}
}
for {
- sz := rand.int_in_range(10, 100000)
- mut new_obj := &MemObj{
- nxt: []&MemObj{len: sz}
+ sz := rand.int_in_range(10, 1000)
+ mut new_obj := &PtrObj{
+ nxt: []&DataObj{len: sz}
}
- sz2 := rand.int_in_range(10, 100000)
- new_obj2 := &MemObj{
- nxt: []&MemObj{len: sz2}
+ sz2 := rand.int_in_range(10, 500000)
+ new_obj2 := &DataObj{
+ data: []f64{len: sz2}
}
- idx2 := rand.int_in_range(0, sz / 2)
+ idx2 := rand.int_in_range(0, sz)
new_obj.nxt[idx2] = new_obj2
// non-equally distributed random index
idx := int(math.sqrt(math.sqrt(rand.f64n(n4))))
@@ -35,9 +46,17 @@ fn waste_mem() {
}
fn main() {
+ mut n_iterations := 5_000_000
+ if os.args.len == 2 {
+ n_iterations = os.args[1].int()
+ }
+ if os.args.len > 2 || n_iterations <= 0 {
+ eprintln('usage:\n\t${os.args[0]} [num_iterations]')
+ exit(1)
+ }
go waste_mem()
mut last := time.sys_mono_now()
- for _ in 0 .. 10_000_000 {
+ for _ in 0 .. n_iterations {
now := time.sys_mono_now()
interval := now - last
println(f64(interval) / f64(time.millisecond))
diff --git a/vlib/v/tests/bench/gcboehm/GC_bench_full.plt b/vlib/v/tests/bench/gcboehm/GC_bench_full.plt
new file mode 100644
index 0000000000..842084e7e4
--- /dev/null
+++ b/vlib/v/tests/bench/gcboehm/GC_bench_full.plt
@@ -0,0 +1,9 @@
+#!/usr/bin/gnuplot -persist
+set title "Boehm-GC: Optimized vs. non-Optimized (Full Mode)"
+set xlabel "Interval #"
+set ylabel "Pause Time [ms]"
+set terminal pdfcairo transparent enhanced fontscale 0.5 size 5.00in, 3.00in
+set output "GC_bench_full.pdf"
+plot "boehm_full.txt" title "full GC" w i, "boehm_full_opt.txt" title "full GC (opt)" w i
+set output
+# EOF
diff --git a/vlib/v/tests/bench/gcboehm/GC_bench_incr.plt b/vlib/v/tests/bench/gcboehm/GC_bench_incr.plt
new file mode 100644
index 0000000000..7608fd435a
--- /dev/null
+++ b/vlib/v/tests/bench/gcboehm/GC_bench_incr.plt
@@ -0,0 +1,9 @@
+#!/usr/bin/gnuplot -persist
+set title "Boehm-GC: Optimized vs. non-Optimized (Generational Mode)"
+set xlabel "Interval #"
+set ylabel "Pause Time [ms]"
+set terminal pdfcairo transparent enhanced fontscale 0.5 size 5.00in, 3.00in
+set output "GC_bench_incr.pdf"
+plot "boehm_incr.txt" title "non-optimized GC" w i, "boehm_incr_opt.txt" title "optimized GC" w i
+set output
+# EOF
diff --git a/vlib/v/tests/bench/gcboehm/GC_bench_non_opt.plt b/vlib/v/tests/bench/gcboehm/GC_bench_non_opt.plt
new file mode 100644
index 0000000000..a841c3e7e4
--- /dev/null
+++ b/vlib/v/tests/bench/gcboehm/GC_bench_non_opt.plt
@@ -0,0 +1,9 @@
+#!/usr/bin/gnuplot -persist
+set title "Boehm-GC: Full vs. Generational Mode (non-opt)"
+set xlabel "Interval #"
+set ylabel "Pause Time [ms]"
+set terminal pdfcairo transparent enhanced fontscale 0.5 size 5.00in, 3.00in
+set output "GC_bench_non_opt.pdf"
+plot "boehm_full.txt" title "full GC" w i, "boehm_incr.txt" title "incr/generational GC" w i
+set output
+# EOF
diff --git a/vlib/v/tests/bench/gcboehm/GC_bench_opt.plt b/vlib/v/tests/bench/gcboehm/GC_bench_opt.plt
new file mode 100644
index 0000000000..932d8fa362
--- /dev/null
+++ b/vlib/v/tests/bench/gcboehm/GC_bench_opt.plt
@@ -0,0 +1,9 @@
+#!/usr/bin/gnuplot -persist
+set title "Boehm-GC: Full vs. Generational Mode (optimized)"
+set xlabel "Interval #"
+set ylabel "Pause Time [ms]"
+set terminal pdfcairo transparent enhanced fontscale 0.5 size 5.00in, 3.00in
+set output "GC_bench_opt.pdf"
+plot "boehm_full_opt.txt" title "full GC" w i, "boehm_incr_opt.txt" title "incr/generational GC" w i
+set output
+# EOF
diff --git a/vlib/v/tests/bench/gcboehm/Makefile b/vlib/v/tests/bench/gcboehm/Makefile
index 2437a73492..f91242c24e 100644
--- a/vlib/v/tests/bench/gcboehm/Makefile
+++ b/vlib/v/tests/bench/gcboehm/Makefile
@@ -1,9 +1,29 @@
.PHONY: all
-all: GC_bench.pdf
+all: GC_bench_non_opt.pdf GC_bench_full.pdf GC_bench_incr.pdf GC_bench_opt.pdf GC_bench.pdf Resources.pdf
-GC_bench.pdf: boehm_full.txt boehm_incr.txt
- gnuplot GC_bench.plt
+GC_bench_non_opt.pdf: GC_bench_non_opt.plt boehm_full.txt boehm_incr.txt
+ gnuplot $<
+ @echo "$@ created"
+
+GC_bench_full.pdf: GC_bench_full.plt boehm_full.txt boehm_full_opt.txt
+ gnuplot $<
+ @echo "$@ created"
+
+GC_bench_incr.pdf: GC_bench_incr.plt boehm_incr.txt boehm_incr_opt.txt
+ gnuplot $<
+ @echo "$@ created"
+
+GC_bench_opt.pdf: GC_bench_opt.plt boehm_full_opt.txt boehm_incr_opt.txt
+ gnuplot $<
+ @echo "$@ created"
+
+GC_bench.pdf: GC_bench.plt boehm_full.txt boehm_incr_opt.txt
+ gnuplot $<
+ @echo "$@ created"
+
+Resources.pdf: Resources.plt resources.txt
+ gnuplot $<
@echo "$@ created"
boehm_full.txt: GC_bench_full
@@ -16,11 +36,31 @@ boehm_incr.txt: GC_bench_incr
sleep 1
./$< > $@
+boehm_full_opt.txt: GC_bench_full_opt
+ sync
+ sleep 1
+ ./$< > $@
+
+boehm_incr_opt.txt: GC_bench_incr_opt
+ sync
+ sleep 1
+ ./$< > $@
+
GC_bench_full: GC_bench.v
v -prod -gc boehm_full -o $@ $<
GC_bench_incr: GC_bench.v
v -prod -gc boehm_incr -o $@ $<
+GC_bench_full_opt: GC_bench.v
+ v -prod -gc boehm_full_opt -o $@ $<
+
+GC_bench_incr_opt: GC_bench.v
+ v -prod -gc boehm_incr_opt -o $@ $<
+
clean:
- rm -f boehm_full.txt boehm_incr.txt GC_bench.pdf GC_bench_full GC_bench_incr
+ rm -f boehm_full.txt boehm_incr.txt boehm_full_opt.txt boehm_incr_opt.txt \
+ GC_bench_non_opt.pdf GC_bench_full.pdf GC_bench_incr.pdf \
+ GC_bench_opt.pdf GC_bench.pdf Resources.pdf \
+ GC_bench_full GC_bench_incr GC_bench_full_opt GC_bench_incr_opt \
+ GC_bench.svg Resources.svg
diff --git a/vlib/v/tests/bench/gcboehm/Resources.plt b/vlib/v/tests/bench/gcboehm/Resources.plt
new file mode 100644
index 0000000000..00ba31463e
--- /dev/null
+++ b/vlib/v/tests/bench/gcboehm/Resources.plt
@@ -0,0 +1,45 @@
+#!/usr/local/bin/gnuplot -persist
+set terminal pdfcairo transparent enhanced fontscale 0.5 size 8.00in, 4.50in
+set output "Resources.pdf"
+set multiplot layout 1,3 title "\nBoehm GC: Resource Requirements for \`GC\\_bench.v\` (2·10^8 Iterations)\n" font ",18"
+set rmargin 9
+set grid noxtics ytics
+set xtics border rotate by -45
+set key box Right samplen 1 spacing 1 height 0.5 opaque
+set style data histogram
+set style histogram clustered gap 1 title textcolor lt -1
+set style fill solid border -1
+#
+set ylabel "Process Memory [GB]"
+plot [-1:4] [0:9.36] "resources.txt" using 3:xtic(1) title "{/Monospace Memory Usage}" lt 2
+#
+set lmargin at screen 0.39
+set ylabel "CPU Usage [% of 1 Core]"
+plot [-1:4] [0:750] "resources.txt" using 5:xtic(1) title "{/Monospace CPU Usage}" lt 7
+#
+set lmargin at screen 0.71
+set ylabel "Time [s]"
+plot [-1:4] [0:210] "resources.txt" using 4:xtic(1) title "{/Monospace Time to Complete}" lt 3
+unset multiplot
+set output
+unset margin
+set terminal svg size 900,530 dynamic enhanced
+set output "Resources.svg"
+set multiplot layout 1,3 title "\nBoehm GC: Resource Requirements for \`GC\\_bench.v\` (2·10^8 Iterations)\n" font ",18"
+#
+set rmargin at screen 0.27
+set ylabel "Process Memory [GB]"
+plot [-1:4] [0:9.36] "resources.txt" using 3:xtic(1) title "{/Monospace Memory Usage}" lt 2
+#
+set lmargin at screen 0.38
+set rmargin at screen 0.59
+set ylabel "CPU Usage [% of 1 Core]"
+plot [-1:4] [0:750] "resources.txt" using 5:xtic(1) title "{/Monospace CPU Usage}" lt 7
+#
+set lmargin at screen 0.71
+unset rmargin
+set ylabel "Time [s]"
+plot [-1:4] [0:210] "resources.txt" using 4:xtic(1) title "{/Monospace Time to Complete}" lt 3
+unset multiplot
+set output
+# EOF
diff --git a/vlib/v/tests/bench/gcboehm/Resources_Ryzen_3800X_Linux.pdf b/vlib/v/tests/bench/gcboehm/Resources_Ryzen_3800X_Linux.pdf
new file mode 100644
index 0000000000..edb00f23d0
Binary files /dev/null and b/vlib/v/tests/bench/gcboehm/Resources_Ryzen_3800X_Linux.pdf differ
diff --git a/vlib/v/tests/bench/gcboehm/Resources_Ryzen_3800X_Linux.svg b/vlib/v/tests/bench/gcboehm/Resources_Ryzen_3800X_Linux.svg
new file mode 100644
index 0000000000..e8bea14b0e
--- /dev/null
+++ b/vlib/v/tests/bench/gcboehm/Resources_Ryzen_3800X_Linux.svg
@@ -0,0 +1,613 @@
+
+
+
diff --git a/vlib/v/tests/bench/gcboehm/resources.txt b/vlib/v/tests/bench/gcboehm/resources.txt
new file mode 100644
index 0000000000..de951eb2cc
--- /dev/null
+++ b/vlib/v/tests/bench/gcboehm/resources.txt
@@ -0,0 +1,6 @@
+# 200000000 iterations
+# flags %MEM MEM[GB] TIME[s] %CPU
+"-gc boehm\\_full" 19.7 6.17 132 680
+"-gc boehm\\_incr" 23.7 7.42 181 625
+"-gc boehm\\_full\\_opt" 14.0 4.38 100 204
+"-gc boehm\\_incr\\_opt" 14.4 4.51 166 186