⚡ replace concurrent sweep with incremental sweep
This commit is contained in:
parent
bde152b6e9
commit
a85a4e37c1
118
src/nasal_gc.cpp
118
src/nasal_gc.cpp
|
@ -6,10 +6,21 @@ namespace nasal {
|
||||||
void gc::do_mark_sweep() {
|
void gc::do_mark_sweep() {
|
||||||
using clk = std::chrono::high_resolution_clock;
|
using clk = std::chrono::high_resolution_clock;
|
||||||
auto begin = clk::now();
|
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();
|
auto mark_end = clk::now();
|
||||||
sweep();
|
sweep();
|
||||||
auto sweep_end = clk::now();
|
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 total_time = (sweep_end-begin).count();
|
||||||
auto mark_time = (mark_end-begin).count();
|
auto mark_time = (mark_end-begin).count();
|
||||||
|
@ -74,15 +85,15 @@ void gc::concurrent_mark(std::vector<var>& vec, usize begin, usize end) {
|
||||||
|
|
||||||
void gc::mark_context_root(std::vector<var>& bfs_queue) {
|
void gc::mark_context_root(std::vector<var>& bfs_queue) {
|
||||||
// scan global
|
// scan global
|
||||||
for(usize i = 0; i<main_context_global_size; ++i) {
|
for(usize i = 0; i < main_context_global_size; ++i) {
|
||||||
auto& val = main_context_global[i];
|
auto& val = main_context_global[i];
|
||||||
if (val.type>vm_type::vm_num) {
|
if (val.type > vm_type::vm_num) {
|
||||||
bfs_queue.push_back(val);
|
bfs_queue.push_back(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// scan now running context, this context maybe related to coroutine or main
|
// scan now running context, this context maybe related to coroutine or main
|
||||||
for(var* i = running_context->stack; i<=running_context->top; ++i) {
|
for(var* i = running_context->stack; i <= running_context->top; ++i) {
|
||||||
if (i->type>vm_type::vm_num) {
|
if (i->type > vm_type::vm_num) {
|
||||||
bfs_queue.push_back(*i);
|
bfs_queue.push_back(*i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,8 +106,8 @@ void gc::mark_context_root(std::vector<var>& bfs_queue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// coroutine is running, so scan main process stack from mctx
|
// coroutine is running, so scan main process stack from mctx
|
||||||
for(var* i = main_context.stack; i<=main_context.top; ++i) {
|
for(var* i = main_context.stack; i <= main_context.top; ++i) {
|
||||||
if (i->type>vm_type::vm_num) {
|
if (i->type > vm_type::vm_num) {
|
||||||
bfs_queue.push_back(*i);
|
bfs_queue.push_back(*i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +131,7 @@ void gc::mark_var(std::vector<var>& bfs_queue, var& value) {
|
||||||
|
|
||||||
void gc::mark_vec(std::vector<var>& bfs_queue, nas_vec& vec) {
|
void gc::mark_vec(std::vector<var>& bfs_queue, nas_vec& vec) {
|
||||||
for(auto& i : vec.elems) {
|
for(auto& i : vec.elems) {
|
||||||
if (i.type>vm_type::vm_num) {
|
if (i.type > vm_type::vm_num) {
|
||||||
bfs_queue.push_back(i);
|
bfs_queue.push_back(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +139,7 @@ void gc::mark_vec(std::vector<var>& bfs_queue, nas_vec& vec) {
|
||||||
|
|
||||||
void gc::mark_hash(std::vector<var>& bfs_queue, nas_hash& hash) {
|
void gc::mark_hash(std::vector<var>& bfs_queue, nas_hash& hash) {
|
||||||
for(auto& i : hash.elems) {
|
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);
|
bfs_queue.push_back(i.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +147,7 @@ void gc::mark_hash(std::vector<var>& bfs_queue, nas_hash& hash) {
|
||||||
|
|
||||||
void gc::mark_func(std::vector<var>& bfs_queue, nas_func& function) {
|
void gc::mark_func(std::vector<var>& bfs_queue, nas_func& function) {
|
||||||
for(auto& i : function.local) {
|
for(auto& i : function.local) {
|
||||||
if (i.type>vm_type::vm_num) {
|
if (i.type > vm_type::vm_num) {
|
||||||
bfs_queue.push_back(i);
|
bfs_queue.push_back(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +158,7 @@ void gc::mark_func(std::vector<var>& bfs_queue, nas_func& function) {
|
||||||
|
|
||||||
void gc::mark_upval(std::vector<var>& bfs_queue, nas_upval& upval) {
|
void gc::mark_upval(std::vector<var>& bfs_queue, nas_upval& upval) {
|
||||||
for(auto& i : upval.elems) {
|
for(auto& i : upval.elems) {
|
||||||
if (i.type>vm_type::vm_num) {
|
if (i.type > vm_type::vm_num) {
|
||||||
bfs_queue.push_back(i);
|
bfs_queue.push_back(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +175,7 @@ void gc::mark_co(std::vector<var>& bfs_queue, nas_co& co) {
|
||||||
bfs_queue.push_back(co.ctx.funcr);
|
bfs_queue.push_back(co.ctx.funcr);
|
||||||
bfs_queue.push_back(co.ctx.upvalr);
|
bfs_queue.push_back(co.ctx.upvalr);
|
||||||
for(var* i = co.ctx.stack; i<=co.ctx.top; ++i) {
|
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);
|
bfs_queue.push_back(*i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,65 +183,36 @@ void gc::mark_co(std::vector<var>& bfs_queue, nas_co& co) {
|
||||||
|
|
||||||
void gc::mark_map(std::vector<var>& bfs_queue, nas_map& mp) {
|
void gc::mark_map(std::vector<var>& bfs_queue, nas_map& mp) {
|
||||||
for(const auto& i : mp.mapper) {
|
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);
|
bfs_queue.push_back(*i.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void gc::sweep() {
|
void gc::sweep() {
|
||||||
if (memory.size() > gc::concurrent_threshold()) {
|
const i64 threshold = 65536 / 4;
|
||||||
flag_concurrent_mark_triggered = true;
|
for (i64 it = 0; it < threshold; ++it) {
|
||||||
usize size = memory.size();
|
if (current_sweep_index - it < 0) {
|
||||||
std::vector<free_list> collect = {{}, {}, {}, {}};
|
break;
|
||||||
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())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
auto i = memory[current_sweep_index - it];
|
||||||
}
|
|
||||||
|
|
||||||
for(auto i : memory) {
|
|
||||||
if (i->mark==nas_val::gc_status::uncollected) {
|
if (i->mark==nas_val::gc_status::uncollected) {
|
||||||
i->clear();
|
|
||||||
unused[static_cast<u8>(i->type)-static_cast<u8>(vm_type::vm_str)].push_back(i);
|
unused[static_cast<u8>(i->type)-static_cast<u8>(vm_type::vm_str)].push_back(i);
|
||||||
i->mark = nas_val::gc_status::collected;
|
i->mark = nas_val::gc_status::collected;
|
||||||
} else if (i->mark==nas_val::gc_status::found) {
|
} else if (i->mark==nas_val::gc_status::found) {
|
||||||
i->mark = nas_val::gc_status::uncollected;
|
i->mark = nas_val::gc_status::uncollected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
current_sweep_index -= threshold;
|
||||||
|
if (current_sweep_index < 0) {
|
||||||
void gc::concurrent_sweep(free_list& collect, usize begin, usize end) {
|
in_sweep_stage = false;
|
||||||
for (usize iter = begin; iter < end; ++ iter) {
|
current_sweep_index = 0;
|
||||||
auto i = memory[iter];
|
|
||||||
if (i->mark==nas_val::gc_status::uncollected) {
|
|
||||||
i->clear();
|
|
||||||
collect[static_cast<u8>(i->type)-static_cast<u8>(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) {
|
void gc::extend(const vm_type type) {
|
||||||
const u8 index = static_cast<u8>(type)-static_cast<u8>(vm_type::vm_str);
|
const u8 index = static_cast<u8>(type)-static_cast<u8>(vm_type::vm_str);
|
||||||
size[index] += incr[index];
|
object_size[index] += incr[index];
|
||||||
|
|
||||||
for(u64 i = 0; i<incr[index]; ++i) {
|
for(u64 i = 0; i<incr[index]; ++i) {
|
||||||
// no need to check, will be killed if memory is not enough
|
// no need to check, will be killed if memory is not enough
|
||||||
|
@ -269,7 +251,7 @@ void gc::init(const std::vector<std::string>& constant_strings,
|
||||||
// initialize counters
|
// initialize counters
|
||||||
worktime = 0;
|
worktime = 0;
|
||||||
for(u8 i = 0; i<gc_type_size; ++i) {
|
for(u8 i = 0; i<gc_type_size; ++i) {
|
||||||
size[i] = gcnt[i] = acnt[i] = 0;
|
object_size[i] = gc_count[i] = alloc_count[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// coroutine pointer set to nullptr
|
// coroutine pointer set to nullptr
|
||||||
|
@ -364,11 +346,11 @@ void gc::info() const {
|
||||||
indent = indent<len? len:indent;
|
indent = indent<len? len:indent;
|
||||||
}
|
}
|
||||||
for(u32 i = 0; i<gc_type_size; ++i) {
|
for(u32 i = 0; i<gc_type_size; ++i) {
|
||||||
len = std::to_string(gcnt[i]).length();
|
len = std::to_string(gc_count[i]).length();
|
||||||
indent = indent<len? len:indent;
|
indent = indent<len? len:indent;
|
||||||
len = std::to_string(acnt[i]).length();
|
len = std::to_string(alloc_count[i]).length();
|
||||||
indent = indent<len? len:indent;
|
indent = indent<len? len:indent;
|
||||||
len = std::to_string(size[i]).length();
|
len = std::to_string(object_size[i]).length();
|
||||||
indent = indent<len? len:indent;
|
indent = indent<len? len:indent;
|
||||||
}
|
}
|
||||||
auto indent_string = std::string("──");
|
auto indent_string = std::string("──");
|
||||||
|
@ -405,14 +387,14 @@ void gc::info() const {
|
||||||
|
|
||||||
double total = 0;
|
double total = 0;
|
||||||
for(u8 i = 0; i<gc_type_size; ++i) {
|
for(u8 i = 0; i<gc_type_size; ++i) {
|
||||||
if (!gcnt[i] && !acnt[i] && !size[i]) {
|
if (!gc_count[i] && !alloc_count[i] && !object_size[i]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
total += static_cast<f64>(gcnt[i]);
|
total += static_cast<f64>(gc_count[i]);
|
||||||
std::clog << "│ " << left << setw(indent) << setfill(' ') << name[i];
|
std::clog << "│ " << left << setw(indent) << setfill(' ') << name[i];
|
||||||
std::clog << " │ " << left << setw(indent) << setfill(' ') << gcnt[i];
|
std::clog << " │ " << left << setw(indent) << setfill(' ') << gc_count[i];
|
||||||
std::clog << " │ " << left << setw(indent) << setfill(' ') << acnt[i];
|
std::clog << " │ " << left << setw(indent) << setfill(' ') << alloc_count[i];
|
||||||
std::clog << " │ " << left << setw(indent) << setfill(' ') << size[i];
|
std::clog << " │ " << left << setw(indent) << setfill(' ') << object_size[i];
|
||||||
std::clog << " │\n";
|
std::clog << " │\n";
|
||||||
}
|
}
|
||||||
std::clog << mid_line << "\n";
|
std::clog << mid_line << "\n";
|
||||||
|
@ -460,17 +442,23 @@ void gc::info() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
var gc::alloc(const vm_type type) {
|
var gc::alloc(const vm_type type) {
|
||||||
const u8 index = static_cast<u8>(type)-static_cast<u8>(vm_type::vm_str);
|
const u32 index = static_cast<u32>(type)-static_cast<u32>(vm_type::vm_str);
|
||||||
++acnt[index];
|
++alloc_count[index];
|
||||||
if (unused[index].empty()) {
|
if (unused[index].empty()) {
|
||||||
++gcnt[index];
|
++gc_count[index];
|
||||||
|
do_mark_sweep();
|
||||||
|
}
|
||||||
|
while (unused[index].empty() && in_sweep_stage) {
|
||||||
do_mark_sweep();
|
do_mark_sweep();
|
||||||
}
|
}
|
||||||
if (unused[index].empty()) {
|
if (unused[index].empty()) {
|
||||||
extend(type);
|
extend(type);
|
||||||
}
|
}
|
||||||
var ret = var::gcobj(unused[index].back());
|
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();
|
unused[index].pop_back();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,8 +52,8 @@ struct gc {
|
||||||
/* heap increase size */
|
/* heap increase size */
|
||||||
u64 incr[gc_type_size] = {
|
u64 incr[gc_type_size] = {
|
||||||
256, // vm_str
|
256, // vm_str
|
||||||
512, // vm_vec
|
256, // vm_vec
|
||||||
512, // vm_hash
|
256, // vm_hash
|
||||||
256, // vm_func
|
256, // vm_func
|
||||||
256, // vm_upval
|
256, // vm_upval
|
||||||
4, // vm_obj
|
4, // vm_obj
|
||||||
|
@ -65,15 +65,18 @@ struct gc {
|
||||||
u64 total_object_count = 0;
|
u64 total_object_count = 0;
|
||||||
|
|
||||||
/* values for analysis */
|
/* values for analysis */
|
||||||
u64 size[gc_type_size];
|
u64 object_size[gc_type_size];
|
||||||
u64 gcnt[gc_type_size];
|
u64 gc_count[gc_type_size];
|
||||||
u64 acnt[gc_type_size];
|
u64 alloc_count[gc_type_size];
|
||||||
i64 worktime = 0;
|
i64 worktime = 0;
|
||||||
i64 max_time = 0;
|
i64 max_time = 0;
|
||||||
i64 max_mark_time = 0;
|
i64 max_mark_time = 0;
|
||||||
i64 max_sweep_time = 0;
|
i64 max_sweep_time = 0;
|
||||||
bool flag_concurrent_mark_triggered = false;
|
bool flag_concurrent_mark_triggered = false;
|
||||||
|
|
||||||
|
bool in_sweep_stage = false;
|
||||||
|
i64 current_sweep_index = 0;
|
||||||
|
|
||||||
void set(context* _ctx, var* _global, usize _size) {
|
void set(context* _ctx, var* _global, usize _size) {
|
||||||
running_context = _ctx;
|
running_context = _ctx;
|
||||||
main_context_global = _global;
|
main_context_global = _global;
|
||||||
|
@ -95,7 +98,6 @@ private:
|
||||||
void mark_co(std::vector<var>&, nas_co&);
|
void mark_co(std::vector<var>&, nas_co&);
|
||||||
void mark_map(std::vector<var>&, nas_map&);
|
void mark_map(std::vector<var>&, nas_map&);
|
||||||
void sweep();
|
void sweep();
|
||||||
void concurrent_sweep(free_list&, usize, usize);
|
|
||||||
|
|
||||||
static const auto concurrent_threshold() {
|
static const auto concurrent_threshold() {
|
||||||
return UINT16_MAX * 16;
|
return UINT16_MAX * 16;
|
||||||
|
|
|
@ -728,7 +728,7 @@ var builtin_gcinfo(context* ctx, gc* ngc) {
|
||||||
|
|
||||||
f64 total = 0;
|
f64 total = 0;
|
||||||
for(u32 i = 0; i<gc_type_size; ++i) {
|
for(u32 i = 0; i<gc_type_size; ++i) {
|
||||||
total += static_cast<f64>(ngc->gcnt[i]);
|
total += ngc->gc_count[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,30 +6,24 @@ var test_func = func(test_processes...) {
|
||||||
test_process_total.stamp();
|
test_process_total.stamp();
|
||||||
|
|
||||||
var time_stamp = maketimestamp();
|
var time_stamp = maketimestamp();
|
||||||
var info = runtime.gc.info();
|
var begin_info = runtime.gc.info();
|
||||||
var gc_total = info.total;
|
var gc_total_begin = begin_info.total;
|
||||||
var duration = 0;
|
|
||||||
foreach (var t; test_processes) {
|
foreach (var f; test_processes) {
|
||||||
var name = t[0];
|
|
||||||
var f = t[1];
|
|
||||||
print("[", os.time(), "] testing ", name, " : ");
|
|
||||||
time_stamp.stamp();
|
|
||||||
f();
|
f();
|
||||||
duration = time_stamp.elapsedMSec();
|
print(".");
|
||||||
info = runtime.gc.info();
|
|
||||||
println(duration, " ms,\tgc ",
|
|
||||||
(info.total-gc_total)*100/duration, "%,\t",
|
|
||||||
1000/duration, " loop/sec"
|
|
||||||
);
|
|
||||||
gc_total = info.total;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println("[", os.time(), "] test time: ",
|
var end_info = runtime.gc.info();
|
||||||
test_process_total.elapsedMSec(), " ms");
|
var gc_total_end = end_info.total;
|
||||||
|
var duration = time_stamp.elapsedMSec();
|
||||||
info = runtime.gc.info();
|
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("+##-gc-----------------");
|
||||||
println("| total : ", info.total, " ms");
|
|
||||||
println("| average : ", info.average, " ms");
|
println("| average : ", info.average, " ms");
|
||||||
println("| max gc : ", info.max_gc, " ms");
|
println("| max gc : ", info.max_gc, " ms");
|
||||||
println("| max mark : ", info.max_mark, " ms");
|
println("| max mark : ", info.max_mark, " ms");
|
||||||
|
@ -37,7 +31,7 @@ var test_func = func(test_processes...) {
|
||||||
println("+----------------------");
|
println("+----------------------");
|
||||||
}
|
}
|
||||||
|
|
||||||
var MAX_ITER_NUM = 2e5;
|
var MAX_ITER_NUM = 5e4;
|
||||||
|
|
||||||
var append_vec = func {
|
var append_vec = func {
|
||||||
var res = [];
|
var res = [];
|
||||||
|
@ -72,7 +66,7 @@ var append_vec_in_vec = func {
|
||||||
var append_hash_in_vec = func {
|
var append_hash_in_vec = func {
|
||||||
var res = [];
|
var res = [];
|
||||||
for(var i=0; i<MAX_ITER_NUM; i+=1) {
|
for(var i=0; i<MAX_ITER_NUM; i+=1) {
|
||||||
append(res, {a:{}, b:{}, c:{}, d:{}});
|
append(res, [{}, {}, {}, {}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,14 +84,56 @@ var append_hash_in_hash = func {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var append_hash_vec_hash = func {
|
||||||
|
var res = [];
|
||||||
|
for(var i=0; i<MAX_ITER_NUM; i+=1) {
|
||||||
|
append(res, {a:[{}], b:[{}], c:[{}], d:[{}]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var append_tree = func {
|
||||||
|
var res = [];
|
||||||
|
for(var i=0; i<MAX_ITER_NUM; i+=1) {
|
||||||
|
append(res, {
|
||||||
|
a: {b: {c:[]}},
|
||||||
|
d: {e: {}},
|
||||||
|
j: {k: {l:{m:[{a:{b:{c:[{}]}}}]}}}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < 10; i += 1) {
|
for (var i = 0; i < 10; i += 1) {
|
||||||
test_func(
|
test_func(
|
||||||
["vec ", append_vec],
|
append_vec,
|
||||||
["hash ", append_hash],
|
append_hash,
|
||||||
["func ", append_func],
|
append_func,
|
||||||
["vec<vec> ", append_vec_in_vec],
|
append_vec_in_vec,
|
||||||
["vec<hash> ", append_hash_in_vec],
|
append_hash_in_vec,
|
||||||
["hash<str, vec> ", append_vec_in_hash],
|
append_vec_in_hash,
|
||||||
["hash<str, hash>", append_hash_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
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
Reference in New Issue