diff --git a/CMakeLists.txt b/CMakeLists.txt index a04118e..c86cd55 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 @@ -97,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) 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/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 35a85a4..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 bfs; mark_context_root(bfs); - // concurrent mark, experimental - if (memory.size()>UINT16_MAX && bfs.size()>32) { - 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); @@ -74,15 +79,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 +100,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 +125,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 +133,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 +141,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 +152,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 +169,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,27 +177,39 @@ 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() { - for(auto i : memory) { + // 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) { - i->clear(); - 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; } } + current_sweep_index -= threshold; + if (current_sweep_index < 0) { + in_incremental_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]; + 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& 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); } } @@ -271,7 +285,7 @@ void gc::clear() { delete i; } memory.clear(); - for(u8 i = 0; i(gcnt[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 << " │\n"; - } - 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"; - - 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*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*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 << 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*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*2+7) << " " << "│\n"; - - std::clog << "│ " << left << setw(indent) << setfill(' ') << "concurrent"; - std::clog << " │ " << setw(indent) - << (flag_concurrent_mark_triggered? "true":"false"); - std::clog << setw(indent*2+7) << " " << "│\n"; - - std::clog << last_line << "\n"; - - wm.restore_code_page(); -} - var gc::alloc(const vm_type type) { - const u8 index = static_cast(type)-static_cast(vm_type::vm_str); - ++acnt[index]; - if (unused[index].empty()) { - ++gcnt[index]; + const u32 index = static_cast(type)-static_cast(vm_type::vm_str); + ++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_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_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->mark = nas_val::gc_status::uncollected; + ret.val.gcobj->clear(); + + // 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(); return ret; } @@ -453,9 +344,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 76631aa..c44e137 100644 --- a/src/nasal_gc.h +++ b/src/nasal_gc.h @@ -17,9 +17,18 @@ #include "nasal.h" #include "nasal_type.h" +#include "util/gc_stat.h" 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; @@ -30,40 +39,37 @@ 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 - std::vector unused[gc_type_size]; // 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] = { - 128, // vm_str - 128, // vm_vec - 64, // vm_hash + u64 incr[GC_TYPE_SIZE] = { + 256, // vm_str + 256, // vm_vec + 256, // 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; + + // total object count + u64 total_object_count = 0; /* values for analysis */ - u64 size[gc_type_size]; - u64 gcnt[gc_type_size]; - u64 acnt[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_incremental_sweep_stage = false; + i64 current_sweep_index = 0; void set(context* _ctx, var* _global, usize _size) { running_context = _ctx; @@ -74,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&); @@ -91,19 +99,18 @@ 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(); 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(); } - double get_total_memory() const { - return total_memory_usage * 1.0 / 1024.0 / 1024.0; + // not very accurate + f64 get_total_memory() const { + return total_object_count * 3.5 / 1024.0 / 1024.0; } public: diff --git a/src/nasal_opcode.cpp b/src/nasal_opcode.cpp index 51e4540..f835237 100644 --- a/src/nasal_opcode.cpp +++ b/src/nasal_opcode.cpp @@ -5,12 +5,31 @@ namespace nasal { void codestream::set(const f64* number_list, const std::string* string_list, + const std::unordered_map& 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_mcallg: + 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 0b9fa1a..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,16 +211,22 @@ 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; + 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_type.h b/src/nasal_type.h index fb283ed..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); @@ -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 }; diff --git a/src/nasal_vm.cpp b/src/nasal_vm.cpp index 84df332..8bd5b2b 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; } } @@ -307,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) { @@ -370,7 +396,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 +405,8 @@ void vm::global_state() { } std::clog << "| " << std::left << std::setw(10) - << std::setfill(' ') << name << " "; + << std::setfill(' ') << name << " " + << std::internal; value_info(global[i]); } } @@ -696,7 +723,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/natives/builtin.cpp b/src/natives/builtin.cpp index 8a4e151..c1a2307 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) { @@ -720,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; i(ngc->gcnt[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/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( 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[]; diff --git a/src/util/gc_stat.cpp b/src/util/gc_stat.cpp new file mode 100644 index 0000000..d6631e5 --- /dev/null +++ b/src/util/gc_stat.cpp @@ -0,0 +1,193 @@ +#include "util/util.h" +#include "util/gc_stat.h" + +#include +#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; +} + +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; +} + +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 + +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; + + 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; + + void dump_info() const; +}; + +} \ No newline at end of file diff --git a/test/gc_test.nas b/test/gc_test.nas index 804fe00..814afff 100644 --- a/test/gc_test.nas +++ b/test/gc_test.nas @@ -6,51 +6,53 @@ 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 f; test_processes) { - println("[", os.time(), "] testing ", id(f)); - 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("[", os.time(), "] ", duration, " ms, gc ", - (info.total-gc_total)*100/duration, "%, ", - 1000/duration, " cps"); - gc_total = info.total; + print("."); } - println("[", os.time(), "] test time: ", - 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("---------------------"); + 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("| 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 = 0.5e5; + var append_vec = func { var res = []; - for(var i=0; i<1e6; i+=1) { - append(res, [1]); + for(var i=0; i