cgen,pref: support `-profile-fns strings__new_builder,i64_str -profile -`

pull/13290/head
Delyan Angelov 2022-01-26 19:52:08 +02:00
parent 65a97a9fbe
commit 3fa9128716
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
7 changed files with 184 additions and 86 deletions

View File

@ -85,6 +85,9 @@ NB: the build flags are shared with the run command too:
c) an average for each function (i.e. (b) / (a) ) c) an average for each function (i.e. (b) / (a) )
d) the function name d) the function name
NB: the profiler is *NOT* currently thread safe, so look at the profile results of
multithreaded programs very sceptically !
NB: if you want to output the profile info to stdout, use `-profile -`. NB: if you want to output the profile info to stdout, use `-profile -`.
NB: you can use `import v.profile`, and then calls to `profile.on(false)` NB: you can use `import v.profile`, and then calls to `profile.on(false)`
@ -94,6 +97,20 @@ NB: the build flags are shared with the run command too:
program's `fn main()` starts, pass `-d no_profile_startup` too. program's `fn main()` starts, pass `-d no_profile_startup` too.
(V constants, and module init() functions are evaluated before `main()` is called) (V constants, and module init() functions are evaluated before `main()` is called)
NB: You can also select specific functions for profiling. For example:
v -profile-fns println,i64_str -profile - run examples/hanoi.v
In this case, the profile counters will be updated only for them, *and* for the functions that they call.
The profile result (after the program finishes), will look similar to this:
127 0.721ms 5680ns println
127 0.693ms 5456ns _writeln_to_fd
127 0.565ms 4449ns _write_buf_to_fd
127 0.045ms 353ns _v_malloc
127 0.017ms 131ns malloc_noscan
127 0.017ms 133ns _v_free
127 0.014ms 113ns vmemmove
127 0.110ms 866ns i64_str
127 0.016ms 127ns tos
-message-limit <limit> -message-limit <limit>
The maximum amount of warnings / errors / notices, that will be accumulated (defaults to 100). The maximum amount of warnings / errors / notices, that will be accumulated (defaults to 100).
The checker will abort prematurely once this limit has been reached. The checker will abort prematurely once this limit has been reached.

View File

@ -96,9 +96,16 @@ fn (mut g Gen) gen_c_main_header() {
if g.pref.is_livemain { if g.pref.is_livemain {
g.generate_hotcode_reloading_main_caller() g.generate_hotcode_reloading_main_caller()
} }
if g.pref.profile_file != '' {
if 'no_profile_startup' in g.pref.compile_defines { if 'no_profile_startup' in g.pref.compile_defines {
g.writeln('vreset_profile_stats();') g.writeln('vreset_profile_stats();')
} }
if g.pref.profile_fns.len > 0 {
g.writeln('vreset_profile_stats();')
// v__profile_enabled will be set true *inside* the fns in g.pref.profile_fns:
g.writeln('v__profile_enabled = false;')
}
}
} }
pub fn (mut g Gen) gen_c_main_footer() { pub fn (mut g Gen) gen_c_main_footer() {

View File

@ -210,45 +210,9 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
eprintln('INFO: compile with `v -live $g.pref.path `, if you want to use the [live] function $node.name .') eprintln('INFO: compile with `v -live $g.pref.path `, if you want to use the [live] function $node.name .')
} }
// //
mut name := node.name mut name := g.c_fn_name(node) or { return }
if name in ['+', '-', '*', '/', '%', '<', '=='] {
name = util.replace_op(name)
}
if node.is_method {
unwrapped_rec_sym := g.table.sym(g.unwrap_generic(node.receiver.typ))
if unwrapped_rec_sym.kind == .placeholder {
return
}
name = g.cc_type(node.receiver.typ, false) + '_' + name
// name = g.table.sym(node.receiver.typ).name + '_' + name
}
if node.language == .c {
name = util.no_dots(name)
} else {
name = c_name(name)
}
mut type_name := g.typ(node.return_type) mut type_name := g.typ(node.return_type)
if node.generic_names.len > 0 {
name = g.generic_fn_name(g.cur_concrete_types, name, true)
}
if g.pref.translated && node.attrs.contains('c') {
// This fixes unknown symbols errors when building separate .c => .v files
// into .o files
//
// example:
// [c: 'P_TryMove']
// fn p_trymove(thing &Mobj_t, x int, y int) bool
//
// =>
//
// bool P_TryMove(main__Mobj_t* thing, int x, int y);
//
// In fn_call every time `p_trymove` is called, `P_TryMove` will be generated instead.
name = node.attrs[0].arg
}
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') && !node.is_main if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') && !node.is_main
&& node.name != 'str' { && node.name != 'str' {
mut key := node.name mut key := node.name
@ -449,6 +413,47 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
} }
} }
fn (mut g Gen) c_fn_name(node &ast.FnDecl) ?string {
mut name := node.name
if name in ['+', '-', '*', '/', '%', '<', '=='] {
name = util.replace_op(name)
}
if node.is_method {
unwrapped_rec_sym := g.table.sym(g.unwrap_generic(node.receiver.typ))
if unwrapped_rec_sym.kind == .placeholder {
return none
}
name = g.cc_type(node.receiver.typ, false) + '_' + name
// name = g.table.sym(node.receiver.typ).name + '_' + name
}
if node.language == .c {
name = util.no_dots(name)
} else {
name = c_name(name)
}
if node.generic_names.len > 0 {
name = g.generic_fn_name(g.cur_concrete_types, name, true)
}
if g.pref.translated && node.attrs.contains('c') {
// This fixes unknown symbols errors when building separate .c => .v files
// into .o files
//
// example:
// [c: 'P_TryMove']
// fn p_trymove(thing &Mobj_t, x int, y int) bool
//
// =>
//
// bool P_TryMove(main__Mobj_t* thing, int x, int y);
//
// In fn_call every time `p_trymove` is called, `P_TryMove` will be generated instead.
name = node.attrs[0].arg
}
return name
}
const closure_ctx = '_V_closure_ctx' const closure_ctx = '_V_closure_ctx'
fn closure_ctx_struct(node ast.FnDecl) string { fn closure_ctx_struct(node ast.FnDecl) string {

View File

@ -14,20 +14,33 @@ fn (mut g Gen) profile_fn(fn_decl ast.FnDecl) {
return return
} }
fn_name := fn_decl.name fn_name := fn_decl.name
cfn_name := g.last_fn_c_name
if fn_name.starts_with('time.vpc_now') || fn_name.starts_with('v.profile.') { if fn_name.starts_with('time.vpc_now') || fn_name.starts_with('v.profile.') {
g.defer_profile_code = '' g.defer_profile_code = ''
} else { } else {
measure_fn_name := if g.pref.os == .macos { 'time__vpc_now_darwin' } else { 'time__vpc_now' } measure_fn_name := if g.pref.os == .macos { 'time__vpc_now_darwin' } else { 'time__vpc_now' }
fn_profile_counter_name := 'vpc_$g.last_fn_c_name' fn_profile_counter_name := 'vpc_$cfn_name'
fn_profile_counter_name_calls := '${fn_profile_counter_name}_calls' fn_profile_counter_name_calls := '${fn_profile_counter_name}_calls'
g.writeln('') g.writeln('')
should_restore_v__profile_enabled := g.pref.profile_fns.len > 0
&& cfn_name in g.pref.profile_fns
if should_restore_v__profile_enabled {
$if trace_profile_fns ? {
eprintln('> profile_fn | $g.pref.profile_fns | $cfn_name')
}
g.writeln('\tbool _prev_v__profile_enabled = v__profile_enabled;')
g.writeln('\tv__profile_enabled = true;')
}
g.writeln('\tdouble _PROF_FN_START = ${measure_fn_name}();') g.writeln('\tdouble _PROF_FN_START = ${measure_fn_name}();')
g.writeln('\tif(v__profile_enabled) { $fn_profile_counter_name_calls++; } // $fn_name') g.writeln('\tif(v__profile_enabled) { $fn_profile_counter_name_calls++; } // $fn_name')
g.writeln('') g.writeln('')
g.defer_profile_code = '\tif(v__profile_enabled) { $fn_profile_counter_name += ${measure_fn_name}() - _PROF_FN_START; }' g.defer_profile_code = '\tif(v__profile_enabled) { $fn_profile_counter_name += ${measure_fn_name}() - _PROF_FN_START; }'
if should_restore_v__profile_enabled {
g.defer_profile_code += '\n\t\tv__profile_enabled = _prev_v__profile_enabled;'
}
g.pcs_declarations.writeln('double $fn_profile_counter_name = 0.0; u64 $fn_profile_counter_name_calls = 0;') g.pcs_declarations.writeln('double $fn_profile_counter_name = 0.0; u64 $fn_profile_counter_name_calls = 0;')
g.pcs << ProfileCounterMeta{ g.pcs << ProfileCounterMeta{
fn_name: g.last_fn_c_name fn_name: cfn_name
vpc_name: fn_profile_counter_name vpc_name: fn_profile_counter_name
vpc_calls: fn_profile_counter_name_calls vpc_calls: fn_profile_counter_name_calls
} }

View File

@ -110,6 +110,7 @@ pub mut:
test_runner string // can be 'simple' (fastest, but much less detailed), 'tap', 'normal' test_runner string // can be 'simple' (fastest, but much less detailed), 'tap', 'normal'
profile_file string // the profile results will be stored inside profile_file profile_file string // the profile results will be stored inside profile_file
profile_no_inline bool // when true, [inline] functions would not be profiled profile_no_inline bool // when true, [inline] functions would not be profiled
profile_fns []string // when set, profiling will be off by default, but inside these functions (and what they call) it will be on.
translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc
is_prod bool // use "-O2" is_prod bool // use "-O2"
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX" obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
@ -424,6 +425,13 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
res.build_options << '$arg $res.profile_file' res.build_options << '$arg $res.profile_file'
i++ i++
} }
'-profile-fns' {
profile_fns := cmdline.option(current_args, arg, '').split(',')
if profile_fns.len > 0 {
res.profile_fns << profile_fns
}
i++
}
'-profile-no-inline' { '-profile-no-inline' {
res.profile_no_inline = true res.profile_no_inline = true
} }

View File

@ -10,40 +10,74 @@ fn test_vexe_exists() {
} }
fn test_v_profile_works() { fn test_v_profile_works() {
os.chdir(vroot) or {} sfile := 'vlib/v/tests/profile/profile_test_1.v'
program_source := os.join_path(vroot, 'vlib/v/tests/profile/profile_test_1.v') validate_output(@FN, '', sfile, {
res := os.execute('${os.quoted_path(vexe)} -profile - run ${os.quoted_path(program_source)}') 'os__init_os_args': 1
// eprintln('res: $res') 'main__main': 1
assert res.exit_code == 0 'println': 1
assert res.output.len > 0 'strconv__atoi': 1
assert res.output.contains(' os__init_os_args') })
assert res.output.contains(' main__main')
assert res.output.contains(' println')
assert res.output.contains(' strconv__atoi')
} }
fn test_v_profile_on_off_api_works() { fn test_v_profile_on_off_api_works() {
os.chdir(vroot) or {} sfile := 'vlib/v/tests/profile/profile_test_2.v'
program_source := os.join_path(vroot, 'vlib/v/tests/profile/profile_test_2.v') res_lines := validate_output(@FN, '', sfile, {
res := os.execute('${os.quoted_path(vexe)} -profile - run ${os.quoted_path(program_source)}') 'builtin_init': 1
// eprintln('res: $res') 'main__main': 1
assert res.exit_code == 0 'main__abc': 1
assert res.output.len > 0 })
assert res.output.contains(' builtin_init')
assert res.output.contains(' main__main')
assert res.output.contains(' main__abc')
res_lines := res.output.split_into_lines()
abc_count := res_lines.filter(it.contains('main__abc'))[0].trim_space().all_before(' ').int()
assert abc_count == 1
// test that `-d no_profile_startup` *also* works: // test that `-d no_profile_startup` *also* works:
res2 := os.execute('${os.quoted_path(vexe)} -d no_profile_startup -profile - run ${os.quoted_path(program_source)}') res2_lines := validate_output(@FN, '-d no_profile_startup', sfile, {
assert res2.exit_code == 0 'builtin_init': -1
assert res2.output.len > 0 'main__main': 1
assert !res2.output.contains(' builtin_init') 'main__abc': 1
assert res2.output.contains(' main__main') })
assert res2.output.contains(' main__abc')
res2_lines := res2.output.split_into_lines()
assert res_lines.len > res2_lines.len assert res_lines.len > res2_lines.len
// dump(res2_lines) // dump(res2_lines)
} }
fn test_v_profile_fns_option_works() {
sfile := 'vlib/v/tests/profile/profile_test_3.v'
validate_output(@FN, '-profile-fns println', sfile, {
'main__main': -1
'main__abc': -1
'main__xyz': -1
'println': 10
})
validate_output(@FN, '-profile-fns main__abc', sfile, {
'main__main': -1
'main__abc': 1
'main__xyz': 2
'println': 10
})
validate_output(@FN, '-profile-fns main__xyz', sfile, {
'main__main': -1
'main__abc': -1
'main__xyz': 2
'println': 10
})
}
fn counter_value(lines []string, what string) int {
res := lines.filter(it.contains(what))
if res.len == 0 {
return -1
}
return res[0].trim_space().all_before(' ').int()
}
fn validate_output(fn_name string, vopts string, fsource string, expected map[string]int) []string {
println('> validating $fn_name with: `v $vopts -profile - run $fsource`')
os.chdir(vroot) or {}
program_source := os.join_path(vroot, fsource)
res := os.execute('${os.quoted_path(vexe)} $vopts -profile - run ${os.quoted_path(program_source)}')
assert res.exit_code == 0
assert res.output.len > 0
res_lines := res.output.split_into_lines()
for expected_counter_name, expected_counter_value in expected {
produced_value := counter_value(res_lines, expected_counter_name)
println(' counter_name: $expected_counter_name')
assert produced_value == expected_counter_value
}
return res_lines
}

View File

@ -0,0 +1,14 @@
fn xyz() {
for i in 0 .. 5 {
println('${@FN} - i: $i')
}
}
fn abc() {
xyz()
xyz()
}
fn main() {
abc()
}