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

@ -77,7 +77,7 @@ NB: the build flags are shared with the run command too:
to fix them first. to fix them first.
-prof, -profile <file.txt> -prof, -profile <file.txt>
Compile the executable with all functions profiled. Compile the executable with all functions profiled.
The profile results will be stored in `file.txt`. The profile results will be stored in `file.txt`.
The format is 4 fields, separated by a space, for each v function: The format is 4 fields, separated by a space, for each v function:
a) how many times it was called a) how many times it was called
@ -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,8 +96,15 @@ 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 'no_profile_startup' in g.pref.compile_defines { if g.pref.profile_file != '' {
g.writeln('vreset_profile_stats();') if 'no_profile_startup' in g.pref.compile_defines {
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;')
}
} }
} }

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

@ -99,20 +99,21 @@ pub mut:
// verbosity VerboseLevel // verbosity VerboseLevel
is_verbose bool is_verbose bool
// nofmt bool // disable vfmt // nofmt bool // disable vfmt
is_test bool // `v test string_test.v` is_test bool // `v test string_test.v`
is_script bool // single file mode (`v program.v`), main function can be skipped is_script bool // single file mode (`v program.v`), main function can be skipped
is_vsh bool // v script (`file.vsh`) file, the `os` module should be made global is_vsh bool // v script (`file.vsh`) file, the `os` module should be made global
is_livemain bool // main program that contains live/hot code is_livemain bool // main program that contains live/hot code
is_liveshared bool // a shared library, that will be used in a -live main program is_liveshared bool // a shared library, that will be used in a -live main program
is_shared bool // an ordinary shared library, -shared, no matter if it is live or not is_shared bool // an ordinary shared library, -shared, no matter if it is live or not
is_o bool // building an .o file is_o bool // building an .o file
is_prof bool // benchmark every function is_prof bool // benchmark every function
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
translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc profile_fns []string // when set, profiling will be off by default, but inside these functions (and what they call) it will be on.
is_prod bool // use "-O2" translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX" is_prod bool // use "-O2"
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
is_repl bool is_repl bool
is_run bool is_run bool
is_debug bool // turned on by -g or -cg, it tells v to pass -g to the C backend compiler. is_debug bool // turned on by -g or -cg, it tells v to pass -g to the C backend compiler.
@ -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()
}