From 065f6ae1623f84a3bb31dd00953f9682561f6fff Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sat, 15 Feb 2025 14:24:08 +0800 Subject: [PATCH 01/13] :art: open return nil if fails --- src/natives/builtin.cpp | 15 +++++++++------ src/natives/io_lib.cpp | 3 ++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/natives/builtin.cpp b/src/natives/builtin.cpp index 8a4e151..30c526b 100644 --- a/src/natives/builtin.cpp +++ b/src/natives/builtin.cpp @@ -380,8 +380,11 @@ var builtin_substr(context* ctx, gc* ngc) { } auto begin = static_cast(beg.num()); auto length = static_cast(len.num()); - if (begin>=str.str().length()) { - return nas_err("native::susbtr", "begin index out of range: "+std::to_string(begin)); + if (begin >= str.str().length()) { + return nas_err( + "native::susbtr", + "begin index out of range: " + std::to_string(begin) + ); } return ngc->newstr(str.str().substr(begin, length)); } @@ -406,7 +409,7 @@ var builtin_left(context* ctx, gc* ngc) { if (!len.is_num()) { return nas_err("native::left", "\"length\" must be number"); } - if (len.num()<0) { + if (len.num() < 0) { return ngc->newstr(""); } return ngc->newstr(str.str().substr(0, len.num())); @@ -426,14 +429,14 @@ var builtin_right(context* ctx, gc* ngc) { i32 length = static_cast(len.num()); i32 srclen = static_cast(str.str().length()); - if (length>srclen) { + if (length > srclen) { length = srclen; } - if (length<0) { + if (length < 0) { length = 0; } - return ngc->newstr(str.str().substr(srclen-length, srclen)); + return ngc->newstr(str.str().substr(srclen - length, srclen)); } var builtin_cmp(context* ctx, gc* ngc) { diff --git a/src/natives/io_lib.cpp b/src/natives/io_lib.cpp index 834491a..8723a06 100644 --- a/src/natives/io_lib.cpp +++ b/src/natives/io_lib.cpp @@ -59,8 +59,9 @@ var builtin_open(context* ctx, gc* ngc) { return nas_err("io::open", "\"mode\" must be string"); } auto file_descriptor = fopen(name.str().c_str(), mode.str().c_str()); + // if failed to open, just return nil for check if (!file_descriptor) { - return nas_err("io::open", "failed to open file <" + name.str() + ">"); + return nil; } var return_object = ngc->alloc(vm_type::vm_ghost); return_object.ghost().set( From a7a2a0a3698c3b42e62bc29d0e34d52b94d631f7 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sat, 8 Mar 2025 01:45:54 +0800 Subject: [PATCH 02/13] :memo: fix typo --- src/cli/cli.cpp | 2 +- src/natives/regex_lib.cpp | 8 ++++---- src/natives/regex_lib.h | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 9961467..b2fe453 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -105,7 +105,7 @@ std::ostream& logo(std::ostream& out) { << " - http://fgprc.org\n" << " - http://fgprc.org.cn\n" << "\n" - << "input to get help.\n\n"; + << "input to get help.\n\n"; return out; } diff --git a/src/natives/regex_lib.cpp b/src/natives/regex_lib.cpp index 4b606cb..7e42f30 100644 --- a/src/natives/regex_lib.cpp +++ b/src/natives/regex_lib.cpp @@ -2,7 +2,7 @@ namespace nasal { -var builtin_regex_match(context* ctx, gc* ngc) { +var builtin_regex_match(context* ctx, gc* ngc) noexcept { auto source = ctx->localr[1]; auto reg_str = ctx->localr[2]; if (!source.is_str()) { @@ -20,7 +20,7 @@ var builtin_regex_match(context* ctx, gc* ngc) { return zero; } -var builtin_regex_search(context* ctx, gc* ngc) { +var builtin_regex_search(context* ctx, gc* ngc) noexcept { auto source = ctx->localr[1]; auto reg_str = ctx->localr[2]; if (!source.is_str()) { @@ -38,7 +38,7 @@ var builtin_regex_search(context* ctx, gc* ngc) { return nil; } -var builtin_regex_replace(context* ctx, gc* ngc) { +var builtin_regex_replace(context* ctx, gc* ngc) noexcept { auto source = ctx->localr[1]; auto reg_str = ctx->localr[2]; auto fmt = ctx->localr[3]; @@ -64,7 +64,7 @@ var builtin_regex_replace(context* ctx, gc* ngc) { return ngc->newstr(source.str()); } -var builtin_regex_match_all(context* ctx, gc* ngc) { +var builtin_regex_match_all(context* ctx, gc* ngc) noexcept { auto source = ctx->localr[1]; auto reg_str = ctx->localr[2]; if (!source.is_str()) { diff --git a/src/natives/regex_lib.h b/src/natives/regex_lib.h index 5e471f5..e912777 100644 --- a/src/natives/regex_lib.h +++ b/src/natives/regex_lib.h @@ -8,10 +8,10 @@ namespace nasal { -var builtin_regex_match(context*, gc*); -var builtin_regex_search(context*, gc*); -var builtin_regex_replace(context*, gc*); -var builtin_regex_match_all(context*, gc*); +var builtin_regex_match(context*, gc*) noexcept; +var builtin_regex_search(context*, gc*) noexcept; +var builtin_regex_replace(context*, gc*) noexcept; +var builtin_regex_match_all(context*, gc*) noexcept; extern nasal_builtin_table regex_lib_native[]; From eeb126ab656715406e1318084198a1bef74bc8a2 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Wed, 12 Mar 2025 21:37:29 +0800 Subject: [PATCH 03/13] :bug: fix global/local debug index --- src/nasal_vm.cpp | 33 +++++++++++++++++++++----- test/gc_test.nas | 60 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/src/nasal_vm.cpp b/src/nasal_vm.cpp index 84df332..0dcfaaa 100644 --- a/src/nasal_vm.cpp +++ b/src/nasal_vm.cpp @@ -225,12 +225,24 @@ void vm::function_call_trace() { std::stack functions; std::stack callsite; - // load call trace + // load call trace, from bottom to top for(var* i = bottom; i <= top; ++i) { // i-1 is the callsite program counter of this function - if (i->is_addr() && i+2<=top && - (i+1)->is_ret() && (i+1)->ret()>0 && - (i+2)->is_func()) { + // +-------+----------------+ + // | func | func() {..} | <-- i + 2 + // +-------+----------------+ + // | ret | 0x3bf | <-- i + 1 (value should not be 0x0) + // +-------+----------------+ + // | addr | 0x7ff5f61ae020 | <-- i + // +-------+----------------+ + // TODO: op_cnt may destroy the order, so maybe this need refact + if (i + 2 > top) { + continue; + } + if (!i->is_addr()) { + continue; + } + if ((i+1)->is_ret() && (i+1)->ret()>0 && (i+2)->is_func()) { functions.push(&(i+2)->func()); callsite.push((i+1)->ret()); } @@ -239,10 +251,18 @@ void vm::function_call_trace() { // another condition may exist // have ret pc on stack, but no function at the top of the ret pc for(var * i = top; i >= bottom; --i) { + // +-------+----------------+ + // | xxxx | xxxx | <-- i + 2 (not function or not exist) + // +-------+----------------+ + // | ret | 0x3bf | <-- i + 1 (value should not be 0x0) + // +-------+----------------+ + // | addr | 0x7ff5f61ae020 | <-- i + // +-------+----------------+ if ((i->is_addr() && i+2<=top && (i+1)->is_ret() && !(i+2)->is_func()) || (i->is_addr() && i+1<=top && i+2>top && (i+1)->is_ret())) { functions.push(&ctx.funcr.func()); callsite.push((i+1)->ret()); + break; } } @@ -370,7 +390,7 @@ void vm::global_state() { << reinterpret_cast(global) << ")\n" << std::dec; for(usize i = 0; i(i) << std::dec << " "; auto name = global_symbol_name[i]; if (name.length()>=10) { @@ -379,7 +399,8 @@ void vm::global_state() { } std::clog << "| " << std::left << std::setw(10) - << std::setfill(' ') << name << " "; + << std::setfill(' ') << name << " " + << std::internal; value_info(global[i]); } } diff --git a/test/gc_test.nas b/test/gc_test.nas index 804fe00..c8f526d 100644 --- a/test/gc_test.nas +++ b/test/gc_test.nas @@ -9,15 +9,18 @@ var test_func = func(test_processes...) { var info = runtime.gc.info(); var gc_total = info.total; var duration = 0; - foreach(var f; test_processes) { - println("[", os.time(), "] testing ", id(f)); + foreach (var t; test_processes) { + var name = t[0]; + var f = t[1]; + print("[", os.time(), "] testing ", name, " : "); time_stamp.stamp(); f(); duration = time_stamp.elapsedMSec(); info = runtime.gc.info(); - println("[", os.time(), "] ", duration, " ms, gc ", + println(duration, " ms, gc ", (info.total-gc_total)*100/duration, "%, ", - 1000/duration, " cps"); + 1000/duration, " count/sec" + ); gc_total = info.total; } @@ -34,23 +37,25 @@ var test_func = func(test_processes...) { println("---------------------"); } +var MAX_ITER_NUM = 2e5; + var append_vec = func { var res = []; - for(var i=0; i<1e6; i+=1) { - append(res, [1]); + for(var i=0; i", append_vec_in_vec], + ["vec", append_hash_in_vec], + ["hash", append_vec_in_hash], + ["hash", append_hash_in_hash] ); } \ No newline at end of file From 705df8dc1d2ee4b42f8d8799de6a4ecd1927f805 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Thu, 13 Mar 2025 00:10:31 +0800 Subject: [PATCH 04/13] :zap: add concurrent sweep --- src/nasal_gc.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- src/nasal_gc.h | 27 ++++++++++++++++++++------- test/gc_test.nas | 32 ++++++++++++++++---------------- 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/nasal_gc.cpp b/src/nasal_gc.cpp index b565c7e..12ed05f 100644 --- a/src/nasal_gc.cpp +++ b/src/nasal_gc.cpp @@ -25,7 +25,7 @@ void gc::mark() { mark_context_root(bfs); // concurrent mark, experimental - if (memory.size()>UINT16_MAX && bfs.size()>32) { + if (memory.size() > gc::concurrent_threshold() && bfs.size() > 16) { flag_concurrent_mark_triggered = true; usize size = bfs.size(); std::thread t0(&gc::concurrent_mark, this, std::ref(bfs), 0, size/4); @@ -179,6 +179,29 @@ void gc::mark_map(std::vector& bfs_queue, nas_map& mp) { } void gc::sweep() { + if (memory.size() > gc::concurrent_threshold()) { + flag_concurrent_mark_triggered = true; + usize size = memory.size(); + std::vector collect = {{}, {}, {}, {}}; + std::thread t0(&gc::concurrent_sweep, this, std::ref(collect[0]), 0, size/4); + std::thread t1(&gc::concurrent_sweep, this, std::ref(collect[1]), size/4, size/2); + std::thread t2(&gc::concurrent_sweep, this, std::ref(collect[2]), size/2, size/4*3); + std::thread t3(&gc::concurrent_sweep, this, std::ref(collect[3]), size/4*3, size); + t0.join(); + t1.join(); + t2.join(); + t3.join(); + + for (auto& i : collect) { + for (int j = 0; j < gc_type_size; ++j) { + for (auto ptr : i[j]) { + unused[j].push_back(ptr); + } + } + } + return; + } + for(auto i : memory) { if (i->mark==nas_val::gc_status::uncollected) { i->clear(); @@ -190,6 +213,19 @@ void gc::sweep() { } } +void gc::concurrent_sweep(free_list& collect, usize begin, usize end) { + for (usize iter = begin; iter < end; ++ iter) { + auto i = memory[iter]; + if (i->mark==nas_val::gc_status::uncollected) { + i->clear(); + collect[static_cast(i->type)-static_cast(vm_type::vm_str)].push_back(i); + i->mark = nas_val::gc_status::collected; + } else if (i->mark==nas_val::gc_status::found) { + i->mark = nas_val::gc_status::uncollected; + } + } +} + void gc::extend(const vm_type type) { const u8 index = static_cast(type)-static_cast(vm_type::vm_str); size[index] += incr[index]; @@ -223,7 +259,7 @@ void gc::extend(const vm_type type) { } // if incr[index] = 1, this will always be 1 - incr[index] = incr[index] + incr[index]/2; + incr[index] = incr[index] + incr[index]; } void gc::init(const std::vector& constant_strings, diff --git a/src/nasal_gc.h b/src/nasal_gc.h index 76631aa..4e3691d 100644 --- a/src/nasal_gc.h +++ b/src/nasal_gc.h @@ -20,6 +20,14 @@ namespace nasal { +struct free_list { + std::vector elem[gc_type_size]; + + auto& operator[](i64 index) { + return elem[index]; + } +}; + struct gc { /* main context temporary storage */ context main_context; @@ -39,18 +47,18 @@ struct gc { std::vector strs = {}; // reserved address for const vm_str std::vector env_argv = {}; // command line arguments std::vector memory; // gc memory - std::vector unused[gc_type_size]; // gc free list + free_list unused; // gc free list /* heap increase size */ u64 incr[gc_type_size] = { - 128, // vm_str - 128, // vm_vec - 64, // vm_hash + 256, // vm_str + 512, // vm_vec + 512, // vm_hash 256, // vm_func 256, // vm_upval - 16, // vm_obj - 16, // vm_co - 2, // vm_map + 4, // vm_obj + 4, // vm_co + 1, // vm_map }; // total memory usage, not very accurate u64 total_memory_usage = 0; @@ -86,6 +94,11 @@ private: void mark_co(std::vector&, nas_co&); void mark_map(std::vector&, nas_map&); void sweep(); + void concurrent_sweep(free_list&, usize, usize); + + static const auto concurrent_threshold() { + return UINT16_MAX * 16; + } public: void extend(const vm_type); diff --git a/test/gc_test.nas b/test/gc_test.nas index c8f526d..6f72d76 100644 --- a/test/gc_test.nas +++ b/test/gc_test.nas @@ -17,9 +17,9 @@ var test_func = func(test_processes...) { f(); duration = time_stamp.elapsedMSec(); info = runtime.gc.info(); - println(duration, " ms, gc ", - (info.total-gc_total)*100/duration, "%, ", - 1000/duration, " count/sec" + println(duration, " ms,\tgc ", + (info.total-gc_total)*100/duration, "%,\t", + 1000/duration, " loop/sec" ); gc_total = info.total; } @@ -28,13 +28,13 @@ var test_func = func(test_processes...) { test_process_total.elapsedMSec(), " ms"); info = runtime.gc.info(); - println("##-gc----------------"); - println("total : ", info.total, " ms"); - println("average : ", info.average, " ms"); - println("max gc : ", info.max_gc, " ms"); - println("max mark : ", info.max_mark, " ms"); - println("max sweep: ", info.max_sweep, " ms"); - println("---------------------"); + println("+##-gc-----------------"); + println("| total : ", info.total, " ms"); + println("| average : ", info.average, " ms"); + println("| max gc : ", info.max_gc, " ms"); + println("| max mark : ", info.max_mark, " ms"); + println("| max sweep: ", info.max_sweep, " ms"); + println("+----------------------"); } var MAX_ITER_NUM = 2e5; @@ -92,12 +92,12 @@ var append_hash_in_hash = func { for (var i = 0; i < 10; i += 1) { test_func( - ["vec", append_vec], - ["hash", append_hash], - ["func", append_func], - ["vec", append_vec_in_vec], - ["vec", append_hash_in_vec], - ["hash", append_vec_in_hash], + ["vec ", append_vec], + ["hash ", append_hash], + ["func ", append_func], + ["vec ", append_vec_in_vec], + ["vec ", append_hash_in_vec], + ["hash ", append_vec_in_hash], ["hash", append_hash_in_hash] ); } \ No newline at end of file From bde152b6e980a7225b49874d9845d52bd07c2561 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Thu, 13 Mar 2025 20:28:21 +0800 Subject: [PATCH 05/13] :zap: optimize --- src/nasal_gc.cpp | 34 ++++++++++++++++++---------------- src/nasal_gc.h | 20 +++++++++++--------- src/nasal_type.h | 2 +- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/nasal_gc.cpp b/src/nasal_gc.cpp index 12ed05f..00975b9 100644 --- a/src/nasal_gc.cpp +++ b/src/nasal_gc.cpp @@ -194,9 +194,11 @@ void gc::sweep() { for (auto& i : collect) { for (int j = 0; j < gc_type_size; ++j) { - for (auto ptr : i[j]) { - unused[j].push_back(ptr); - } + unused[j].insert( + unused[j].end(), + std::make_move_iterator(i[j].begin()), + std::make_move_iterator(i[j].end()) + ); } } return; @@ -240,21 +242,21 @@ void gc::extend(const vm_type type) { } switch(type) { case vm_type::vm_str: - total_memory_usage += incr[index] * sizeof(std::string); break; + total_object_count += incr[index] * sizeof(std::string); break; case vm_type::vm_vec: - total_memory_usage += incr[index] * sizeof(nas_vec); break; + total_object_count += incr[index] * sizeof(nas_vec); break; case vm_type::vm_hash: - total_memory_usage += incr[index] * sizeof(nas_hash); break; + total_object_count += incr[index] * sizeof(nas_hash); break; case vm_type::vm_func: - total_memory_usage += incr[index] * sizeof(nas_func); break; + total_object_count += incr[index] * sizeof(nas_func); break; case vm_type::vm_upval: - total_memory_usage += incr[index] * sizeof(nas_upval); break; + total_object_count += incr[index] * sizeof(nas_upval); break; case vm_type::vm_ghost: - total_memory_usage += incr[index] * sizeof(nas_ghost); break; + total_object_count += incr[index] * sizeof(nas_ghost); break; case vm_type::vm_co: - total_memory_usage += incr[index] * sizeof(nas_co); break; + total_object_count += incr[index] * sizeof(nas_co); break; case vm_type::vm_map: - total_memory_usage += incr[index] * sizeof(nas_map); break; + total_object_count += incr[index] * sizeof(nas_map); break; default: break; } @@ -283,22 +285,22 @@ void gc::init(const std::vector& constant_strings, strs[i] = var::gcobj(new nas_val(vm_type::vm_str)); strs[i].val.gcobj->immutable = 1; strs[i].str() = constant_strings[i]; - total_memory_usage += strs[i].str().size(); - total_memory_usage += sizeof(std::string); + total_object_count += strs[i].str().size(); + total_object_count += sizeof(std::string); } // record arguments env_argv.resize(argv.size()); for (u64 i = 0; i < argv.size(); ++i) { // incremental initialization, avoid memory leak in repl mode - if (env_argv[i].is_str() && env_argv[i].str()==argv[i]) { + if (env_argv[i].is_str() && env_argv[i].str() == argv[i]) { continue; } env_argv[i] = var::gcobj(new nas_val(vm_type::vm_str)); env_argv[i].val.gcobj->immutable = 1; env_argv[i].str() = argv[i]; - total_memory_usage += env_argv[i].str().size(); - total_memory_usage += sizeof(std::string); + total_object_count += env_argv[i].str().size(); + total_object_count += sizeof(std::string); } } diff --git a/src/nasal_gc.h b/src/nasal_gc.h index 4e3691d..36adead 100644 --- a/src/nasal_gc.h +++ b/src/nasal_gc.h @@ -38,16 +38,16 @@ struct gc { /* runtime context */ context* running_context = nullptr; - nas_co* cort = nullptr; // running coroutine + nas_co* cort = nullptr; // running coroutine - /* temporary space used in native/module functions */ + /* temporary space used in native / module functions */ var temp = nil; /* constants and memory pool */ - std::vector strs = {}; // reserved address for const vm_str - std::vector env_argv = {}; // command line arguments - std::vector memory; // gc memory - free_list unused; // gc free list + std::vector strs = {}; // reserved address for const vm_str + std::vector env_argv = {}; // command line arguments + std::vector memory; // gc memory + free_list unused; // gc free list /* heap increase size */ u64 incr[gc_type_size] = { @@ -60,8 +60,9 @@ struct gc { 4, // vm_co 1, // vm_map }; - // total memory usage, not very accurate - u64 total_memory_usage = 0; + + // total object count + u64 total_object_count = 0; /* values for analysis */ u64 size[gc_type_size]; @@ -115,8 +116,9 @@ public: return worktime * 1.0 / den * 1000.0; } + // not very accurate double get_total_memory() const { - return total_memory_usage * 1.0 / 1024.0 / 1024.0; + return total_object_count * 3.5 / 1024.0 / 1024.0; } public: diff --git a/src/nasal_type.h b/src/nasal_type.h index fb283ed..7cda862 100644 --- a/src/nasal_type.h +++ b/src/nasal_type.h @@ -51,7 +51,7 @@ struct nas_map; // mapper // nas_val includes gc-managed types struct nas_val { enum class gc_status: u8 { - uncollected = 0, + uncollected = 0, collected, found }; From a85a4e37c112543150a2efeb7d7e767cd95bca84 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sat, 15 Mar 2025 01:12:23 +0800 Subject: [PATCH 06/13] :zap: replace concurrent sweep with incremental sweep --- src/nasal_gc.cpp | 118 ++++++++++++++++++---------------------- src/nasal_gc.h | 14 +++-- src/natives/builtin.cpp | 2 +- test/gc_test.nas | 94 ++++++++++++++++++++++---------- 4 files changed, 127 insertions(+), 101 deletions(-) diff --git a/src/nasal_gc.cpp b/src/nasal_gc.cpp index 00975b9..311289d 100644 --- a/src/nasal_gc.cpp +++ b/src/nasal_gc.cpp @@ -6,10 +6,21 @@ namespace nasal { void gc::do_mark_sweep() { using clk = std::chrono::high_resolution_clock; auto begin = clk::now(); - mark(); + if (!in_sweep_stage) { + mark(); + in_sweep_stage = true; + current_sweep_index = memory.size() - 1; + } auto mark_end = clk::now(); sweep(); auto sweep_end = clk::now(); + if (!in_sweep_stage) { + for (auto i : memory) { + if (i->mark == nas_val::gc_status::found) { + i->mark = nas_val::gc_status::uncollected; + } + } + } auto total_time = (sweep_end-begin).count(); auto mark_time = (mark_end-begin).count(); @@ -74,15 +85,15 @@ void gc::concurrent_mark(std::vector& vec, usize begin, usize end) { void gc::mark_context_root(std::vector& bfs_queue) { // scan global - for(usize i = 0; ivm_type::vm_num) { + if (val.type > vm_type::vm_num) { bfs_queue.push_back(val); } } // scan now running context, this context maybe related to coroutine or main - for(var* i = running_context->stack; i<=running_context->top; ++i) { - if (i->type>vm_type::vm_num) { + for(var* i = running_context->stack; i <= running_context->top; ++i) { + if (i->type > vm_type::vm_num) { bfs_queue.push_back(*i); } } @@ -95,8 +106,8 @@ void gc::mark_context_root(std::vector& bfs_queue) { } // coroutine is running, so scan main process stack from mctx - for(var* i = main_context.stack; i<=main_context.top; ++i) { - if (i->type>vm_type::vm_num) { + for(var* i = main_context.stack; i <= main_context.top; ++i) { + if (i->type > vm_type::vm_num) { bfs_queue.push_back(*i); } } @@ -120,7 +131,7 @@ void gc::mark_var(std::vector& bfs_queue, var& value) { void gc::mark_vec(std::vector& bfs_queue, nas_vec& vec) { for(auto& i : vec.elems) { - if (i.type>vm_type::vm_num) { + if (i.type > vm_type::vm_num) { bfs_queue.push_back(i); } } @@ -128,7 +139,7 @@ void gc::mark_vec(std::vector& bfs_queue, nas_vec& vec) { void gc::mark_hash(std::vector& bfs_queue, nas_hash& hash) { for(auto& i : hash.elems) { - if (i.second.type>vm_type::vm_num) { + if (i.second.type > vm_type::vm_num) { bfs_queue.push_back(i.second); } } @@ -136,7 +147,7 @@ void gc::mark_hash(std::vector& bfs_queue, nas_hash& hash) { void gc::mark_func(std::vector& bfs_queue, nas_func& function) { for(auto& i : function.local) { - if (i.type>vm_type::vm_num) { + if (i.type > vm_type::vm_num) { bfs_queue.push_back(i); } } @@ -147,7 +158,7 @@ void gc::mark_func(std::vector& bfs_queue, nas_func& function) { void gc::mark_upval(std::vector& bfs_queue, nas_upval& upval) { for(auto& i : upval.elems) { - if (i.type>vm_type::vm_num) { + if (i.type > vm_type::vm_num) { bfs_queue.push_back(i); } } @@ -164,7 +175,7 @@ void gc::mark_co(std::vector& bfs_queue, nas_co& co) { bfs_queue.push_back(co.ctx.funcr); bfs_queue.push_back(co.ctx.upvalr); for(var* i = co.ctx.stack; i<=co.ctx.top; ++i) { - if (i->type>vm_type::vm_num) { + if (i->type > vm_type::vm_num) { bfs_queue.push_back(*i); } } @@ -172,65 +183,36 @@ void gc::mark_co(std::vector& bfs_queue, nas_co& co) { void gc::mark_map(std::vector& bfs_queue, nas_map& mp) { for(const auto& i : mp.mapper) { - if (i.second->type>vm_type::vm_num) { + if (i.second->type > vm_type::vm_num) { bfs_queue.push_back(*i.second); } } } void gc::sweep() { - if (memory.size() > gc::concurrent_threshold()) { - flag_concurrent_mark_triggered = true; - usize size = memory.size(); - std::vector collect = {{}, {}, {}, {}}; - std::thread t0(&gc::concurrent_sweep, this, std::ref(collect[0]), 0, size/4); - std::thread t1(&gc::concurrent_sweep, this, std::ref(collect[1]), size/4, size/2); - std::thread t2(&gc::concurrent_sweep, this, std::ref(collect[2]), size/2, size/4*3); - std::thread t3(&gc::concurrent_sweep, this, std::ref(collect[3]), size/4*3, size); - t0.join(); - t1.join(); - t2.join(); - t3.join(); - - for (auto& i : collect) { - for (int j = 0; j < gc_type_size; ++j) { - unused[j].insert( - unused[j].end(), - std::make_move_iterator(i[j].begin()), - std::make_move_iterator(i[j].end()) - ); - } + const i64 threshold = 65536 / 4; + for (i64 it = 0; it < threshold; ++it) { + if (current_sweep_index - it < 0) { + break; } - return; - } - - for(auto i : memory) { + auto i = memory[current_sweep_index - it]; if (i->mark==nas_val::gc_status::uncollected) { - i->clear(); unused[static_cast(i->type)-static_cast(vm_type::vm_str)].push_back(i); i->mark = nas_val::gc_status::collected; } else if (i->mark==nas_val::gc_status::found) { i->mark = nas_val::gc_status::uncollected; } } -} - -void gc::concurrent_sweep(free_list& collect, usize begin, usize end) { - for (usize iter = begin; iter < end; ++ iter) { - auto i = memory[iter]; - if (i->mark==nas_val::gc_status::uncollected) { - i->clear(); - collect[static_cast(i->type)-static_cast(vm_type::vm_str)].push_back(i); - i->mark = nas_val::gc_status::collected; - } else if (i->mark==nas_val::gc_status::found) { - i->mark = nas_val::gc_status::uncollected; - } + current_sweep_index -= threshold; + if (current_sweep_index < 0) { + in_sweep_stage = false; + current_sweep_index = 0; } } void gc::extend(const vm_type type) { const u8 index = static_cast(type)-static_cast(vm_type::vm_str); - size[index] += incr[index]; + object_size[index] += incr[index]; for(u64 i = 0; i& constant_strings, // initialize counters worktime = 0; for(u8 i = 0; i(gcnt[i]); + total += static_cast(gc_count[i]); std::clog << "│ " << left << setw(indent) << setfill(' ') << name[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << gcnt[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << acnt[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << size[i]; + std::clog << " │ " << left << setw(indent) << setfill(' ') << gc_count[i]; + std::clog << " │ " << left << setw(indent) << setfill(' ') << alloc_count[i]; + std::clog << " │ " << left << setw(indent) << setfill(' ') << object_size[i]; std::clog << " │\n"; } std::clog << mid_line << "\n"; @@ -460,17 +442,23 @@ void gc::info() const { } var gc::alloc(const vm_type type) { - const u8 index = static_cast(type)-static_cast(vm_type::vm_str); - ++acnt[index]; + const u32 index = static_cast(type)-static_cast(vm_type::vm_str); + ++alloc_count[index]; if (unused[index].empty()) { - ++gcnt[index]; + ++gc_count[index]; + do_mark_sweep(); + } + while (unused[index].empty() && in_sweep_stage) { do_mark_sweep(); } if (unused[index].empty()) { extend(type); } var ret = var::gcobj(unused[index].back()); - ret.val.gcobj->mark = nas_val::gc_status::uncollected; + ret.val.gcobj->clear(); + ret.val.gcobj->mark = in_sweep_stage + ? nas_val::gc_status::found + : nas_val::gc_status::uncollected; unused[index].pop_back(); return ret; } diff --git a/src/nasal_gc.h b/src/nasal_gc.h index 36adead..82bdecf 100644 --- a/src/nasal_gc.h +++ b/src/nasal_gc.h @@ -52,8 +52,8 @@ struct gc { /* heap increase size */ u64 incr[gc_type_size] = { 256, // vm_str - 512, // vm_vec - 512, // vm_hash + 256, // vm_vec + 256, // vm_hash 256, // vm_func 256, // vm_upval 4, // vm_obj @@ -65,15 +65,18 @@ struct gc { u64 total_object_count = 0; /* values for analysis */ - u64 size[gc_type_size]; - u64 gcnt[gc_type_size]; - u64 acnt[gc_type_size]; + u64 object_size[gc_type_size]; + u64 gc_count[gc_type_size]; + u64 alloc_count[gc_type_size]; i64 worktime = 0; i64 max_time = 0; i64 max_mark_time = 0; i64 max_sweep_time = 0; bool flag_concurrent_mark_triggered = false; + bool in_sweep_stage = false; + i64 current_sweep_index = 0; + void set(context* _ctx, var* _global, usize _size) { running_context = _ctx; main_context_global = _global; @@ -95,7 +98,6 @@ private: void mark_co(std::vector&, nas_co&); void mark_map(std::vector&, nas_map&); void sweep(); - void concurrent_sweep(free_list&, usize, usize); static const auto concurrent_threshold() { return UINT16_MAX * 16; diff --git a/src/natives/builtin.cpp b/src/natives/builtin.cpp index 30c526b..bc9602b 100644 --- a/src/natives/builtin.cpp +++ b/src/natives/builtin.cpp @@ -728,7 +728,7 @@ var builtin_gcinfo(context* ctx, gc* ngc) { f64 total = 0; for(u32 i = 0; i(ngc->gcnt[i]); + total += ngc->gc_count[i]; } diff --git a/test/gc_test.nas b/test/gc_test.nas index 6f72d76..7cc002a 100644 --- a/test/gc_test.nas +++ b/test/gc_test.nas @@ -6,30 +6,24 @@ var test_func = func(test_processes...) { test_process_total.stamp(); var time_stamp = maketimestamp(); - var info = runtime.gc.info(); - var gc_total = info.total; - var duration = 0; - foreach (var t; test_processes) { - var name = t[0]; - var f = t[1]; - print("[", os.time(), "] testing ", name, " : "); - time_stamp.stamp(); + var begin_info = runtime.gc.info(); + var gc_total_begin = begin_info.total; + + foreach (var f; test_processes) { f(); - duration = time_stamp.elapsedMSec(); - info = runtime.gc.info(); - println(duration, " ms,\tgc ", - (info.total-gc_total)*100/duration, "%,\t", - 1000/duration, " loop/sec" - ); - gc_total = info.total; + print("."); } - println("[", os.time(), "] test time: ", - test_process_total.elapsedMSec(), " ms"); - - info = runtime.gc.info(); + var end_info = runtime.gc.info(); + var gc_total_end = end_info.total; + var duration = time_stamp.elapsedMSec(); + println(" ", duration, " ms,\tgc ", + int((gc_total_end-gc_total_begin)*100/duration), "%,\t", + int(1000/(duration/size(test_processes))*10)/10, " test(s)/sec" + ); + + var info = runtime.gc.info(); println("+##-gc-----------------"); - println("| total : ", info.total, " ms"); println("| average : ", info.average, " ms"); println("| max gc : ", info.max_gc, " ms"); println("| max mark : ", info.max_mark, " ms"); @@ -37,7 +31,7 @@ var test_func = func(test_processes...) { println("+----------------------"); } -var MAX_ITER_NUM = 2e5; +var MAX_ITER_NUM = 5e4; var append_vec = func { var res = []; @@ -72,7 +66,7 @@ var append_vec_in_vec = func { var append_hash_in_vec = func { var res = []; for(var i=0; i ", append_vec_in_vec], - ["vec ", append_hash_in_vec], - ["hash ", append_vec_in_hash], - ["hash", append_hash_in_hash] + append_vec, + append_hash, + append_func, + append_vec_in_vec, + append_hash_in_vec, + append_vec_in_hash, + append_hash_in_hash, + + append_vec, + append_hash, + append_func, + append_vec_in_vec, + append_hash_in_vec, + append_vec_in_hash, + append_hash_in_hash, + + append_hash_vec_hash, + append_hash_vec_hash, + append_hash_vec_hash, + + append_tree, + append_tree, + append_tree, + + append_hash_vec_hash, + append_hash_vec_hash, + append_hash_vec_hash, + + append_tree, + append_tree, + append_tree ); } \ No newline at end of file From a83978c553fcce4a72bd5728c8751a66974378f0 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sat, 15 Mar 2025 02:14:44 +0800 Subject: [PATCH 07/13] :memo: add comments --- src/nasal_gc.cpp | 22 ++++++++++++++-------- test/gc_test.nas | 18 +++++++++++++++++- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/nasal_gc.cpp b/src/nasal_gc.cpp index 311289d..2e0a9e8 100644 --- a/src/nasal_gc.cpp +++ b/src/nasal_gc.cpp @@ -13,14 +13,14 @@ void gc::do_mark_sweep() { } auto mark_end = clk::now(); sweep(); + // if (!in_sweep_stage) { + // for (auto i : memory) { + // if (i->mark == nas_val::gc_status::found) { + // i->mark = nas_val::gc_status::uncollected; + // } + // } + // } auto sweep_end = clk::now(); - if (!in_sweep_stage) { - for (auto i : memory) { - if (i->mark == nas_val::gc_status::found) { - i->mark = nas_val::gc_status::uncollected; - } - } - } auto total_time = (sweep_end-begin).count(); auto mark_time = (mark_end-begin).count(); @@ -208,6 +208,7 @@ void gc::sweep() { in_sweep_stage = false; current_sweep_index = 0; } + return; } void gc::extend(const vm_type type) { @@ -444,13 +445,18 @@ void gc::info() const { var gc::alloc(const vm_type type) { const u32 index = static_cast(type)-static_cast(vm_type::vm_str); ++alloc_count[index]; - if (unused[index].empty()) { + // if still in incremental sweep stage? do it + // if not in incremental sweep stage, run a new gc cycle + if (in_sweep_stage || unused[index].empty()) { ++gc_count[index]; do_mark_sweep(); } + // if in incremental sweep stage, but the unused list is empty, + // do it until the unused list has something while (unused[index].empty() && in_sweep_stage) { do_mark_sweep(); } + // after all gc stages, still get empty list, extend if (unused[index].empty()) { extend(type); } diff --git a/test/gc_test.nas b/test/gc_test.nas index 7cc002a..cfcdf4c 100644 --- a/test/gc_test.nas +++ b/test/gc_test.nas @@ -31,7 +31,7 @@ var test_func = func(test_processes...) { println("+----------------------"); } -var MAX_ITER_NUM = 5e4; +var MAX_ITER_NUM = 1e5; var append_vec = func { var res = []; @@ -132,6 +132,22 @@ for (var i = 0; i < 10; i += 1) { append_hash_vec_hash, append_hash_vec_hash, + append_tree, + append_tree, + append_tree, + + append_hash_vec_hash, + append_hash_vec_hash, + append_hash_vec_hash, + + append_tree, + append_tree, + append_tree, + + append_hash_vec_hash, + append_hash_vec_hash, + append_hash_vec_hash, + append_tree, append_tree, append_tree From ac8e5c6361e4d6434efa3e9ffd53d08e9f14b101 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sat, 15 Mar 2025 17:36:07 +0800 Subject: [PATCH 08/13] :zap: optimize incremental gc --- CMakeLists.txt | 1 + src/nasal_gc.cpp | 148 ++++++++++++++++++++-------------------- src/nasal_gc.h | 25 +++---- src/nasal_type.h | 2 +- src/natives/builtin.cpp | 26 +++---- src/util/gc_stat.cpp | 59 ++++++++++++++++ src/util/gc_stat.h | 55 +++++++++++++++ test/gc_test.nas | 17 +++-- 8 files changed, 219 insertions(+), 114 deletions(-) create mode 100644 src/util/gc_stat.cpp create mode 100644 src/util/gc_stat.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a04118e..a37b4dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ set(NASAL_OBJECT_SOURCE_FILE ${CMAKE_SOURCE_DIR}/src/natives/unix_lib.cpp ${CMAKE_SOURCE_DIR}/src/repl/repl.cpp ${CMAKE_SOURCE_DIR}/src/util/fs.cpp + ${CMAKE_SOURCE_DIR}/src/util/gc_stat.cpp ${CMAKE_SOURCE_DIR}/src/util/util.cpp ${CMAKE_SOURCE_DIR}/src/ast_dumper.cpp ${CMAKE_SOURCE_DIR}/src/ast_format.cpp diff --git a/src/nasal_gc.cpp b/src/nasal_gc.cpp index 2e0a9e8..fab1854 100644 --- a/src/nasal_gc.cpp +++ b/src/nasal_gc.cpp @@ -4,31 +4,27 @@ namespace nasal { void gc::do_mark_sweep() { - using clk = std::chrono::high_resolution_clock; - auto begin = clk::now(); - if (!in_sweep_stage) { - mark(); - in_sweep_stage = true; - current_sweep_index = memory.size() - 1; + count_mark_time(); + count_sweep_time(); +} + +void gc::count_mark_time() { + if (in_incremental_sweep_stage) { + return; } - auto mark_end = clk::now(); + + status.stamp(); + mark(); + status.elapsed_mark_time(); + + in_incremental_sweep_stage = true; + current_sweep_index = memory.size() - 1; +} + +void gc::count_sweep_time() { + status.stamp(); sweep(); - // if (!in_sweep_stage) { - // for (auto i : memory) { - // if (i->mark == nas_val::gc_status::found) { - // i->mark = nas_val::gc_status::uncollected; - // } - // } - // } - auto sweep_end = clk::now(); - - auto total_time = (sweep_end-begin).count(); - auto mark_time = (mark_end-begin).count(); - auto sweep_time = (sweep_end-mark_end).count(); - worktime += total_time; - max_time = max_time gc::concurrent_threshold() && bfs.size() > 16) { - flag_concurrent_mark_triggered = true; + status.flag_concurrent_mark_triggered = true; usize size = bfs.size(); std::thread t0(&gc::concurrent_mark, this, std::ref(bfs), 0, size/4); std::thread t1(&gc::concurrent_mark, this, std::ref(bfs), size/4, size/2); @@ -190,14 +186,17 @@ void gc::mark_map(std::vector& bfs_queue, nas_map& mp) { } void gc::sweep() { - const i64 threshold = 65536 / 4; + // if threshold is too small, too many allocated objects will be marked as "found" + // objects with "found" will be marked to "uncollected" in the next gc cycle + // this will cause memory wasting. + const i64 threshold = 4096; for (i64 it = 0; it < threshold; ++it) { if (current_sweep_index - it < 0) { break; } auto i = memory[current_sweep_index - it]; if (i->mark==nas_val::gc_status::uncollected) { - unused[static_cast(i->type)-static_cast(vm_type::vm_str)].push_back(i); + unused[static_cast(i->type)-static_cast(vm_type::vm_str)].push_back(i); i->mark = nas_val::gc_status::collected; } else if (i->mark==nas_val::gc_status::found) { i->mark = nas_val::gc_status::uncollected; @@ -205,15 +204,14 @@ void gc::sweep() { } current_sweep_index -= threshold; if (current_sweep_index < 0) { - in_sweep_stage = false; + in_incremental_sweep_stage = false; current_sweep_index = 0; } - return; } void gc::extend(const vm_type type) { - const u8 index = static_cast(type)-static_cast(vm_type::vm_str); - object_size[index] += incr[index]; + const u32 index = static_cast(type)-static_cast(vm_type::vm_str); + status.object_size[index] += incr[index]; for(u64 i = 0; i& constant_strings, const std::vector& argv) { - // initialize counters - worktime = 0; - for(u8 i = 0; i(gc_count[i]); + total += static_cast(status.gc_cycle_trigger_count[i]); std::clog << "│ " << left << setw(indent) << setfill(' ') << name[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << gc_count[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << alloc_count[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << object_size[i]; - std::clog << " │\n"; + std::clog << " │ " << left << setw(indent) << setfill(' ') << status.gc_cycle_trigger_count[i]; + std::clog << " │ " << left << setw(indent) << setfill(' ') << status.alloc_count[i]; + std::clog << " │ " << left << setw(indent) << setfill(' ') << status.object_size[i]; + std::clog << " │\n" << std::internal; } std::clog << mid_line << "\n"; @@ -407,62 +404,67 @@ void gc::info() const { std::clog << " " << left << setw(indent) << setfill(' ') << " "; std::clog << " │\n" << another_mid_line << "\n"; - const auto gc_time = worktime*1.0/den*1000; std::clog << "│ " << left << setw(indent) << setfill(' ') << "gc time"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << gc_time << " ms"; + std::clog << " │ " << setw(indent-3) << setprecision(4) << status.gc_time_ms() << " ms"; std::clog << setw(indent*2+7) << " " << "│\n"; - const auto avg_time = worktime*1.0/den*1000/total; std::clog << "│ " << left << setw(indent) << setfill(' ') << "avg time"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << avg_time << " ms"; + std::clog << " │ " << setw(indent-3) << setprecision(4) << status.avg_time_ms() << " ms"; std::clog << setw(indent*2+7) << " " << "│\n"; - const auto max_gc = max_time*1.0/den*1000; - std::clog << "│ " << left << setw(indent) << setfill(' ') << "max gc"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << max_gc << " ms"; + std::clog << "│ " << left << setw(indent) << setfill(' ') << "avg mark"; + std::clog << " │ " << setw(indent-3) << setprecision(4) << status.avg_mark_time_ms() << " ms"; + std::clog << setw(indent*2+7) << " " << "│\n"; + + std::clog << "│ " << left << setw(indent) << setfill(' ') << "avg sweep"; + std::clog << " │ " << setw(indent-3) << setprecision(4) << status.avg_sweep_time_ms() << " ms"; std::clog << setw(indent*2+7) << " " << "│\n"; - const auto max_mark = max_mark_time*1.0/den*1000; std::clog << "│ " << left << setw(indent) << setfill(' ') << "max mark"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << max_mark << " ms"; + std::clog << " │ " << setw(indent-3) << setprecision(4) << status.max_mark_time_ms() << " ms"; std::clog << setw(indent*2+7) << " " << "│\n"; - const auto max_sweep = max_sweep_time*1.0/den*1000; std::clog << "│ " << left << setw(indent) << setfill(' ') << "max sweep"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << max_sweep << " ms"; + std::clog << " │ " << setw(indent-3) << setprecision(4) << status.max_sweep_time_ms() << " ms"; std::clog << setw(indent*2+7) << " " << "│\n"; std::clog << "│ " << left << setw(indent) << setfill(' ') << "concurrent"; std::clog << " │ " << setw(indent) - << (flag_concurrent_mark_triggered? "true":"false"); + << (status.flag_concurrent_mark_triggered? "true":"false"); std::clog << setw(indent*2+7) << " " << "│\n"; - std::clog << last_line << "\n"; + std::clog << last_line << "\n" << std::internal; wm.restore_code_page(); } var gc::alloc(const vm_type type) { const u32 index = static_cast(type)-static_cast(vm_type::vm_str); - ++alloc_count[index]; + ++status.alloc_count[index]; // if still in incremental sweep stage? do it // if not in incremental sweep stage, run a new gc cycle - if (in_sweep_stage || unused[index].empty()) { - ++gc_count[index]; + if (in_incremental_sweep_stage) { + do_mark_sweep(); + } else if (unused[index].empty()) { + ++status.gc_cycle_trigger_count[index]; do_mark_sweep(); } // if in incremental sweep stage, but the unused list is empty, // do it until the unused list has something - while (unused[index].empty() && in_sweep_stage) { + while (unused[index].empty() && in_incremental_sweep_stage) { do_mark_sweep(); } // after all gc stages, still get empty list, extend if (unused[index].empty()) { extend(type); } + var ret = var::gcobj(unused[index].back()); ret.val.gcobj->clear(); - ret.val.gcobj->mark = in_sweep_stage + + // if incremental sweep stage, mark it as found + // but be aware that it may be collected in next gc cycle + ret.val.gcobj->mark = in_incremental_sweep_stage ? nas_val::gc_status::found : nas_val::gc_status::uncollected; unused[index].pop_back(); @@ -485,9 +487,9 @@ void gc::context_change(nas_co* co) { void gc::context_reserve() { // pc = 0 means this coroutine is finished - cort->status = running_context->pc? - nas_co::status::suspended: - nas_co::status::dead; + cort->status = running_context->pc + ? nas_co::status::suspended + : nas_co::status::dead; // store running state to coroutine cort->ctx = *running_context; diff --git a/src/nasal_gc.h b/src/nasal_gc.h index 82bdecf..18c384f 100644 --- a/src/nasal_gc.h +++ b/src/nasal_gc.h @@ -17,11 +17,12 @@ #include "nasal.h" #include "nasal_type.h" +#include "util/gc_stat.h" namespace nasal { struct free_list { - std::vector elem[gc_type_size]; + std::vector elem[GC_TYPE_SIZE]; auto& operator[](i64 index) { return elem[index]; @@ -50,7 +51,7 @@ struct gc { free_list unused; // gc free list /* heap increase size */ - u64 incr[gc_type_size] = { + u64 incr[GC_TYPE_SIZE] = { 256, // vm_str 256, // vm_vec 256, // vm_hash @@ -65,16 +66,9 @@ struct gc { u64 total_object_count = 0; /* values for analysis */ - u64 object_size[gc_type_size]; - u64 gc_count[gc_type_size]; - u64 alloc_count[gc_type_size]; - i64 worktime = 0; - i64 max_time = 0; - i64 max_mark_time = 0; - i64 max_sweep_time = 0; - bool flag_concurrent_mark_triggered = false; + gc_stat status; - bool in_sweep_stage = false; + bool in_incremental_sweep_stage = false; i64 current_sweep_index = 0; void set(context* _ctx, var* _global, usize _size) { @@ -86,6 +80,8 @@ struct gc { private: /* gc functions */ void do_mark_sweep(); + void count_mark_time(); + void count_sweep_time(); void mark(); void concurrent_mark(std::vector&, usize, usize); void mark_context_root(std::vector&); @@ -113,13 +109,12 @@ public: void context_reserve(); public: - double get_gc_time_ms() const { - const auto den = std::chrono::high_resolution_clock::duration::period::den; - return worktime * 1.0 / den * 1000.0; + f64 get_gc_time_ms() const { + return status.gc_time_ms(); } // not very accurate - double get_total_memory() const { + f64 get_total_memory() const { return total_object_count * 3.5 / 1024.0 / 1024.0; } diff --git a/src/nasal_type.h b/src/nasal_type.h index 7cda862..4fb622b 100644 --- a/src/nasal_type.h +++ b/src/nasal_type.h @@ -35,7 +35,7 @@ enum class vm_type: u8 { }; // size of gc object type -const u32 gc_type_size = +const u32 GC_TYPE_SIZE = static_cast(vm_type::vm_type_size_max) - static_cast(vm_type::vm_str); diff --git a/src/natives/builtin.cpp b/src/natives/builtin.cpp index bc9602b..c1a2307 100644 --- a/src/natives/builtin.cpp +++ b/src/natives/builtin.cpp @@ -723,27 +723,17 @@ var builtin_gcextend(context* ctx, gc* ngc) { } var builtin_gcinfo(context* ctx, gc* ngc) { - const auto den = std::chrono::high_resolution_clock::duration::period::den; var res = ngc->alloc(vm_type::vm_hash); - - f64 total = 0; - for(u32 i = 0; igc_count[i]; - } - - auto& map = res.hash().elems; - const auto worktime = static_cast(ngc->worktime); - const auto max_time = static_cast(ngc->max_time); - const auto max_mark_time = static_cast(ngc->max_mark_time); - const auto max_sweep_time = static_cast(ngc->max_sweep_time); - // using ms - map["total"] = var::num(worktime/den*1000); - map["average"] = var::num(worktime/den*1000/total); - map["max_gc"] = var::num(max_time/den*1000); - map["max_mark"] = var::num(max_mark_time/den*1000); - map["max_sweep"] = var::num(max_sweep_time/den*1000); + map["total"] = var::num(ngc->status.gc_time_ms()); + map["average"] = var::num(ngc->status.avg_time_ms()); + map["mark_count"] = var::num(ngc->status.total_mark_count); + map["sweep_count"] = var::num(ngc->status.total_sweep_count); + map["avg_mark"] = var::num(ngc->status.avg_mark_time_ms()); + map["avg_sweep"] = var::num(ngc->status.avg_sweep_time_ms()); + map["max_mark"] = var::num(ngc->status.max_mark_time_ms()); + map["max_sweep"] = var::num(ngc->status.max_sweep_time_ms()); return res; } diff --git a/src/util/gc_stat.cpp b/src/util/gc_stat.cpp new file mode 100644 index 0000000..5bac2f6 --- /dev/null +++ b/src/util/gc_stat.cpp @@ -0,0 +1,59 @@ +#include "util/gc_stat.h" + +#include + +namespace nasal { + +void gc_stat::init() { + for (i64 i = 0; i < GC_TYPE_SIZE; i++) { + object_size[i] = 0; + alloc_count[i] = 0; + gc_cycle_trigger_count[i] = 0; + } + + total_mark_count = 0; + total_sweep_count = 0; + + total_mark_time = 0; + total_sweep_time = 0; + + max_mark_time = 0; + max_sweep_time = 0; + + flag_concurrent_mark_triggered = false; +} + +f64 gc_stat::gc_time_ms() const { + const auto den = std::chrono::high_resolution_clock::duration::period::den; + return ((total_mark_time + total_sweep_time) * 1000.0) / den; +} + +f64 gc_stat::avg_time_ms() const { + u64 total_gc_cycle = 0; + for (i64 i = 0; i < GC_TYPE_SIZE; i++) { + total_gc_cycle += gc_cycle_trigger_count[i]; + } + return gc_time_ms() / total_mark_count; +} + +f64 gc_stat::avg_mark_time_ms() const { + const auto den = std::chrono::high_resolution_clock::duration::period::den; + return (total_mark_time * 1000.0) / den / total_mark_count; +} + +f64 gc_stat::avg_sweep_time_ms() const { + const auto den = std::chrono::high_resolution_clock::duration::period::den; + return (total_sweep_time * 1000.0) / den / total_sweep_count; +} + +f64 gc_stat::max_mark_time_ms() const { + const auto den = std::chrono::high_resolution_clock::duration::period::den; + return (max_mark_time * 1000.0) / den; +} + +f64 gc_stat::max_sweep_time_ms() const { + const auto den = std::chrono::high_resolution_clock::duration::period::den; + return (max_sweep_time * 1000.0) / den; +} + +} \ No newline at end of file diff --git a/src/util/gc_stat.h b/src/util/gc_stat.h new file mode 100644 index 0000000..ca0083e --- /dev/null +++ b/src/util/gc_stat.h @@ -0,0 +1,55 @@ +#include "nasal.h" +#include "nasal_type.h" + +#include + +namespace nasal { + +struct gc_stat { + u64 object_size[GC_TYPE_SIZE]; + u64 gc_cycle_trigger_count[GC_TYPE_SIZE]; + u64 alloc_count[GC_TYPE_SIZE]; + + u64 total_mark_count = 0; + u64 total_sweep_count = 0; + + i64 total_mark_time = 0; + i64 total_sweep_time = 0; + + i64 max_mark_time = 0; + i64 max_sweep_time = 0; + + bool flag_concurrent_mark_triggered = false; + + std::chrono::time_point start_time; + + void stamp() { + start_time = std::chrono::high_resolution_clock::now(); + } + + void elapsed_mark_time() { + auto end = std::chrono::high_resolution_clock::now(); + auto mark_time = (end - start_time).count(); + ++total_mark_count; + total_mark_time += mark_time; + max_mark_time = max_mark_time > mark_time ? max_mark_time : mark_time; + } + + void elapsed_sweep_time() { + auto end = std::chrono::high_resolution_clock::now(); + auto sweep_time = (end - start_time).count(); + ++total_sweep_count; + total_sweep_time += sweep_time; + max_sweep_time = max_sweep_time > sweep_time ? max_sweep_time : sweep_time; + } + + void init(); + f64 gc_time_ms() const; + f64 avg_time_ms() const; + f64 avg_mark_time_ms() const; + f64 avg_sweep_time_ms() const; + f64 max_mark_time_ms() const; + f64 max_sweep_time_ms() const; +}; + +} \ No newline at end of file diff --git a/test/gc_test.nas b/test/gc_test.nas index cfcdf4c..814afff 100644 --- a/test/gc_test.nas +++ b/test/gc_test.nas @@ -23,15 +23,18 @@ var test_func = func(test_processes...) { ); var info = runtime.gc.info(); - println("+##-gc-----------------"); - println("| average : ", info.average, " ms"); - println("| max gc : ", info.max_gc, " ms"); - println("| max mark : ", info.max_mark, " ms"); - println("| max sweep: ", info.max_sweep, " ms"); - println("+----------------------"); + println("+##-gc----------------------"); + println("| avg gc cycle : ", int(1000 / info.average), " exec/sec"); + println("| avg mark : ", int(1000 / info.avg_mark), " exec/sec"); + println("| avg sweep : ", int(1000 / info.avg_sweep), " exec/sec"); + println("| mark count : ", info.mark_count); + println("| sweep count : ", info.sweep_count); + println("| max mark : ", info.max_mark, " ms"); + println("| max sweep : ", info.max_sweep, " ms"); + println("+---------------------------"); } -var MAX_ITER_NUM = 1e5; +var MAX_ITER_NUM = 0.5e5; var append_vec = func { var res = []; From 10d296519796f18fd507c0859bd8029aa423daf3 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sat, 15 Mar 2025 17:58:13 +0800 Subject: [PATCH 09/13] :art: improve codes --- src/nasal_dbg.cpp | 2 +- src/nasal_gc.cpp | 149 +------------------------------------------ src/nasal_gc.h | 5 -- src/nasal_opcode.h | 3 +- src/nasal_vm.cpp | 2 +- src/util/gc_stat.cpp | 138 ++++++++++++++++++++++++++++++++++++++- src/util/gc_stat.h | 4 +- 7 files changed, 144 insertions(+), 159 deletions(-) diff --git a/src/nasal_dbg.cpp b/src/nasal_dbg.cpp index 35a85a4..94cbfd8 100644 --- a/src/nasal_dbg.cpp +++ b/src/nasal_dbg.cpp @@ -290,7 +290,7 @@ void dbg::run(const codegen& gen, counter.dump_all_code_line_counter(std::clog): counter.dump_this_file_line_counter(std::clog); } - ngc.info(); + ngc.status.dump_info(); ngc.clear(); imm.clear(); return; diff --git a/src/nasal_gc.cpp b/src/nasal_gc.cpp index fab1854..ba56bc4 100644 --- a/src/nasal_gc.cpp +++ b/src/nasal_gc.cpp @@ -1,5 +1,4 @@ #include "nasal_gc.h" -#include "util/util.h" namespace nasal { @@ -31,10 +30,9 @@ void gc::mark() { std::vector bfs; mark_context_root(bfs); - // concurrent mark, experimental - if (memory.size() > gc::concurrent_threshold() && bfs.size() > 16) { - status.flag_concurrent_mark_triggered = true; - usize size = bfs.size(); + // concurrent mark + if (memory.size() > UINT16_MAX * 16 && bfs.size() > 16) { + auto size = bfs.size(); std::thread t0(&gc::concurrent_mark, this, std::ref(bfs), 0, size/4); std::thread t1(&gc::concurrent_mark, this, std::ref(bfs), size/4, size/2); std::thread t2(&gc::concurrent_mark, this, std::ref(bfs), size/2, size/4*3); @@ -297,147 +295,6 @@ void gc::clear() { env_argv.clear(); } -void gc::info() const { - util::windows_code_page_manager wm; - wm.set_utf8_output(); - - using std::left; - using std::setw; - using std::setfill; - using std::setprecision; - - const char* used_table_name[] = { - "object type", - "gc cycle", - "alloc count", - "object count", - "detail", - "time spend", - "gc time", - "avg time", - "max gc", - "max mark", - "max sweep", - nullptr - }; - const char* name[] = { - "string", - "vector", - "hashmap", - "function", - "upvalue", - "ghost", - "coroutine", - "namespace", - nullptr - }; - - usize indent = 0, len = 0; - for(usize i = 0; used_table_name[i]; ++i) { - len = std::string(used_table_name[i]).length(); - indent = indent(status.gc_cycle_trigger_count[i]); - std::clog << "│ " << left << setw(indent) << setfill(' ') << name[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << status.gc_cycle_trigger_count[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << status.alloc_count[i]; - std::clog << " │ " << left << setw(indent) << setfill(' ') << status.object_size[i]; - std::clog << " │\n" << std::internal; - } - std::clog << mid_line << "\n"; - - const auto den = std::chrono::high_resolution_clock::duration::period::den; - std::clog << "│ " << left << setw(indent) << setfill(' ') << "detail"; - std::clog << " │ " << left << setw(indent) << setfill(' ') << "time spend"; - std::clog << " " << left << setw(indent) << setfill(' ') << " "; - std::clog << " " << left << setw(indent) << setfill(' ') << " "; - std::clog << " │\n" << another_mid_line << "\n"; - - std::clog << "│ " << left << setw(indent) << setfill(' ') << "gc time"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << status.gc_time_ms() << " ms"; - std::clog << setw(indent*2+7) << " " << "│\n"; - - std::clog << "│ " << left << setw(indent) << setfill(' ') << "avg time"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << status.avg_time_ms() << " ms"; - std::clog << setw(indent*2+7) << " " << "│\n"; - - std::clog << "│ " << left << setw(indent) << setfill(' ') << "avg mark"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << status.avg_mark_time_ms() << " ms"; - std::clog << setw(indent*2+7) << " " << "│\n"; - - std::clog << "│ " << left << setw(indent) << setfill(' ') << "avg sweep"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << status.avg_sweep_time_ms() << " ms"; - std::clog << setw(indent*2+7) << " " << "│\n"; - - std::clog << "│ " << left << setw(indent) << setfill(' ') << "max mark"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << status.max_mark_time_ms() << " ms"; - std::clog << setw(indent*2+7) << " " << "│\n"; - - std::clog << "│ " << left << setw(indent) << setfill(' ') << "max sweep"; - std::clog << " │ " << setw(indent-3) << setprecision(4) << status.max_sweep_time_ms() << " ms"; - std::clog << setw(indent*2+7) << " " << "│\n"; - - std::clog << "│ " << left << setw(indent) << setfill(' ') << "concurrent"; - std::clog << " │ " << setw(indent) - << (status.flag_concurrent_mark_triggered? "true":"false"); - std::clog << setw(indent*2+7) << " " << "│\n"; - - std::clog << last_line << "\n" << std::internal; - - wm.restore_code_page(); -} - var gc::alloc(const vm_type type) { const u32 index = static_cast(type)-static_cast(vm_type::vm_str); ++status.alloc_count[index]; diff --git a/src/nasal_gc.h b/src/nasal_gc.h index 18c384f..c44e137 100644 --- a/src/nasal_gc.h +++ b/src/nasal_gc.h @@ -95,15 +95,10 @@ private: void mark_map(std::vector&, nas_map&); void sweep(); - static const auto concurrent_threshold() { - return UINT16_MAX * 16; - } - public: void extend(const vm_type); void init(const std::vector&, const std::vector&); void clear(); - void info() const; var alloc(const vm_type); void context_change(nas_co*); void context_reserve(); diff --git a/src/nasal_opcode.h b/src/nasal_opcode.h index 0b9fa1a..9201ff8 100644 --- a/src/nasal_opcode.h +++ b/src/nasal_opcode.h @@ -215,8 +215,7 @@ public: const nasal_builtin_table*, const std::string* file_list = nullptr); void dump(std::ostream&) const; + friend std::ostream& operator<<(std::ostream&, const codestream&); }; -std::ostream& operator<<(std::ostream&, const codestream&); - } \ No newline at end of file diff --git a/src/nasal_vm.cpp b/src/nasal_vm.cpp index 0dcfaaa..96b9388 100644 --- a/src/nasal_vm.cpp +++ b/src/nasal_vm.cpp @@ -717,7 +717,7 @@ void vm::run(const codegen& gen, // all nasal programs should end here vmexit: if (verbose) { - ngc.info(); + ngc.status.dump_info(); } imm.clear(); if (!is_repl_mode) { diff --git a/src/util/gc_stat.cpp b/src/util/gc_stat.cpp index 5bac2f6..d6631e5 100644 --- a/src/util/gc_stat.cpp +++ b/src/util/gc_stat.cpp @@ -1,6 +1,8 @@ +#include "util/util.h" #include "util/gc_stat.h" #include +#include namespace nasal { @@ -19,8 +21,6 @@ void gc_stat::init() { max_mark_time = 0; max_sweep_time = 0; - - flag_concurrent_mark_triggered = false; } f64 gc_stat::gc_time_ms() const { @@ -56,4 +56,138 @@ f64 gc_stat::max_sweep_time_ms() const { return (max_sweep_time * 1000.0) / den; } +void gc_stat::dump_info() const { + util::windows_code_page_manager wm; + wm.set_utf8_output(); + + using std::left; + using std::setw; + using std::setfill; + using std::setprecision; + + const char* used_table_name[] = { + "object type", + "gc cycle", + "alloc count", + "object count", + "detail", + "time spend", + "gc time", + "avg time", + "max gc", + "max mark", + "max sweep" + }; + const char* name[] = { + "string", + "vector", + "hashmap", + "function", + "upvalue", + "ghost", + "coroutine", + "namespace" + }; + + // calculate max indent length + usize indent = 0; + for (auto tname : used_table_name) { + auto len = strlen(tname); + indent = indent start_time; void stamp() { @@ -50,6 +48,8 @@ struct gc_stat { f64 avg_sweep_time_ms() const; f64 max_mark_time_ms() const; f64 max_sweep_time_ms() const; + + void dump_info() const; }; } \ No newline at end of file From b5f02de883c4799bdd31ad9fd2c70e7122607861 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sat, 15 Mar 2025 18:17:35 +0800 Subject: [PATCH 10/13] :art: opcode dump can get global variable name --- src/nasal_codegen.cpp | 1 + src/nasal_codegen.h | 10 +++++----- src/nasal_dbg.cpp | 8 +++++++- src/nasal_opcode.cpp | 24 +++++++++++++++++++++++- src/nasal_opcode.h | 11 +++++++++++ src/nasal_vm.cpp | 8 +++++++- 6 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/nasal_codegen.cpp b/src/nasal_codegen.cpp index 0a513de..4455082 100644 --- a/src/nasal_codegen.cpp +++ b/src/nasal_codegen.cpp @@ -1436,6 +1436,7 @@ void codegen::print(std::ostream& out) { codestream::set( const_number_table.data(), const_string_table.data(), + global, native_function.data() ); diff --git a/src/nasal_codegen.h b/src/nasal_codegen.h index ae86d3c..4f0e9a8 100644 --- a/src/nasal_codegen.h +++ b/src/nasal_codegen.h @@ -161,11 +161,11 @@ private: void ret_gen(return_expr*); public: - const auto& strs() const {return const_string_table;} - const auto& nums() const {return const_number_table;} - const auto& natives() const {return native_function;} - const auto& codes() const {return code;} - const auto& globals() const {return global;} + const auto& strs() const { return const_string_table; } + const auto& nums() const { return const_number_table; } + const auto& natives() const { return native_function; } + const auto& codes() const { return code; } + const auto& globals() const { return global; } public: codegen() = default; diff --git a/src/nasal_dbg.cpp b/src/nasal_dbg.cpp index 94cbfd8..490b301 100644 --- a/src/nasal_dbg.cpp +++ b/src/nasal_dbg.cpp @@ -163,7 +163,13 @@ void dbg::step_info() { begin = (ctx.pc>>3)==0? 0:((ctx.pc>>3)<<3); end = (1+(ctx.pc>>3))<<3; - codestream::set(const_number, const_string, native_function.data(), files); + codestream::set( + const_number, + const_string, + global_symbol_name, + native_function.data(), + files + ); std::clog << "\nnext bytecode:\n"; for(u64 i = begin; i& globals, const nasal_builtin_table* native_table, const std::string* file_list) { const_number = number_list; const_string = string_list; natives = native_table; files = file_list; + + global_variable.resize(globals.size()); + for(auto& [name, index]: globals) { + global_variable[index] = name; + } +} + +void codestream::set(const f64* number_list, + const std::string* string_list, + const std::vector& globals, + const nasal_builtin_table* native_table, + const std::string* file_list) { + const_number = number_list; + const_string = string_list; + natives = native_table; + files = file_list; + + global_variable = globals; } void codestream::dump(std::ostream& out) const { @@ -93,13 +112,16 @@ void codestream::dump(std::ostream& out) const { case op_jmp: case op_jt: case op_jf: - case op_callg: case op_mcallg: case op_loadg: case op_calll: case op_mcalll: case op_loadl: out << hex << "0x" << num << dec; break; + case op_callg: + out << hex << "0x" << num << dec; + out << " (" << util::rawstr(global_variable[num], 32) << ")"; + break; case op_callb: out << hex << "0x" << num << dec; out << " <" << natives[num].name << "@0x"; diff --git a/src/nasal_opcode.h b/src/nasal_opcode.h index 9201ff8..e12d94b 100644 --- a/src/nasal_opcode.h +++ b/src/nasal_opcode.h @@ -4,6 +4,10 @@ #include "natives/builtin.h" #include +#include +#include +#include +#include namespace nasal { @@ -207,11 +211,18 @@ private: inline static const std::string* const_string = nullptr; inline static const nasal_builtin_table* natives = nullptr; inline static const std::string* files = nullptr; + inline static std::vector global_variable; public: codestream(const opcode& c, const u64 i): code(c), index(i) {} static void set(const f64*, const std::string*, + const std::unordered_map&, + const nasal_builtin_table*, + const std::string* file_list = nullptr); + static void set(const f64*, + const std::string*, + const std::vector&, const nasal_builtin_table*, const std::string* file_list = nullptr); void dump(std::ostream&) const; diff --git a/src/nasal_vm.cpp b/src/nasal_vm.cpp index 96b9388..8bd5b2b 100644 --- a/src/nasal_vm.cpp +++ b/src/nasal_vm.cpp @@ -327,7 +327,13 @@ void vm::trace_back() { std::clog << "\nback trace "; std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n"; - codestream::set(const_number, const_string, native_function.data(), files); + codestream::set( + const_number, + const_string, + global_symbol_name, + native_function.data(), + files + ); for(u64 p = 0, same = 0, prev = 0xffffffff; !ret.empty(); prev = p, ret.pop()) { if ((p = ret.top())==prev) { From dd9d5baf22f0e5da780028403710217d9181a043 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sat, 15 Mar 2025 18:39:19 +0800 Subject: [PATCH 11/13] :art: mcallg also --- src/nasal_opcode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nasal_opcode.cpp b/src/nasal_opcode.cpp index 9b53baf..f835237 100644 --- a/src/nasal_opcode.cpp +++ b/src/nasal_opcode.cpp @@ -112,12 +112,12 @@ void codestream::dump(std::ostream& out) const { case op_jmp: case op_jt: case op_jf: - case op_mcallg: case op_loadg: case op_calll: case op_mcalll: case op_loadl: out << hex << "0x" << num << dec; break; + case op_mcallg: case op_callg: out << hex << "0x" << num << dec; out << " (" << util::rawstr(global_variable[num], 32) << ")"; From e724aa6ef2ac7740de3c934dc24a66e4e51d9b3a Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sun, 16 Mar 2025 02:19:17 +0800 Subject: [PATCH 12/13] :memo: fuck mac --- src/util/gc_stat.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/gc_stat.h b/src/util/gc_stat.h index 7c7b420..db45753 100644 --- a/src/util/gc_stat.h +++ b/src/util/gc_stat.h @@ -1,3 +1,5 @@ +#pragma once + #include "nasal.h" #include "nasal_type.h" From 906f337f1a26806f1fdc5a294e1271bc9ecd9854 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Sun, 16 Mar 2025 02:21:47 +0800 Subject: [PATCH 13/13] :fire: fuck mac --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a37b4dd..c86cd55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,7 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/module) set(MODULE_USED_OBJECT_SOURCE_FILE ${CMAKE_SOURCE_DIR}/src/util/util.cpp + ${CMAKE_SOURCE_DIR}/src/util/gc_stat.cpp ${CMAKE_SOURCE_DIR}/src/util/fs.cpp ${CMAKE_SOURCE_DIR}/src/nasal_type.cpp ${CMAKE_SOURCE_DIR}/src/nasal_gc.cpp)