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 @@ + + + +Gnuplot +Produced by GNUPLOT 5.2 patchlevel 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 10 + + + + + + + + + + + + + 20 + + + + + + + + + + + + + 30 + + + + + + + + + + + + + 40 + + + + + + + + + + + + + 50 + + + + + + + + + + + + + 60 + + + + + + + + + + + + + 70 + + + + + + + + + + + + + 80 + + + + + + + + + + + + + 90 + + + + + 0 + + + + + 1x106 + + + + + 2x106 + + + + + 3x106 + + + + + 4x106 + + + + + 5x106 + + + + + + + + + Pause Time [ms] + + + + + Interval # + + + + + Boehm-GC: Pause Times - Classic vs. Optimized Modes + + + + + + + -gc boehm_full + + + + -gc boehm_incr_opt + + + + -gc boehm_full_opt + + + + + + + + + -gc boehm_full + + + -gc boehm_full + + + + + + -gc boehm_incr_opt + + + -gc boehm_incr_opt + + + + + + -gc boehm_full_opt + + + -gc boehm_full_opt + + + + + + + + + + + + + + + + + + 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 @@ + + + +Gnuplot +Produced by GNUPLOT 5.2 patchlevel 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Boehm GC: Resource Requirements for `GC_bench.v` (2·108 Iterations) + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 1 + + + + + + + + + + + + + 2 + + + + + + + + + + + + + 3 + + + + + + + + + + + + + 4 + + + + + + + + + + + + + 5 + + + + + + + + + + + + + 6 + + + + + + + + + + + + + 7 + + + + + + + + + + + + + 8 + + + + + + + + + + + + + 9 + + + + + -gc boehm_full + + + + + -gc boehm_incr + + + + + -gc boehm_full_opt + + + + + -gc boehm_incr_opt + + + + + + + + + Process Memory [GB] + + + + + + + Memory Usage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Memory Usage + + + Memory Usage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 100 + + + + + + + + + + + + + 200 + + + + + + + + + + + + + 300 + + + + + + + + + + + + + 400 + + + + + + + + + + + + + 500 + + + + + + + + + + + + + 600 + + + + + + + + + + + + + 700 + + + + + -gc boehm_full + + + + + -gc boehm_incr + + + + + -gc boehm_full_opt + + + + + -gc boehm_incr_opt + + + + + + + + + CPU Usage [% of 1 Core] + + + + + + + CPU Usage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CPU Usage + + + CPU Usage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 50 + + + + + + + + + + + + + 100 + + + + + + + + + + + + + 150 + + + + + + + + + + + + + 200 + + + + + -gc boehm_full + + + + + -gc boehm_incr + + + + + -gc boehm_full_opt + + + + + -gc boehm_incr_opt + + + + + + + + + Time [s] + + + + + + + Time to Complete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Time to Complete + + + Time to Complete + + + + + + + + + + + + + + + + + + + + + + + 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