v/cmd/tools/performance_compare.v

195 lines
8.4 KiB
V

import os
import flag
import scripting
import vgit
const (
tool_version = '0.0.5'
tool_description = " Compares V executable size and performance,
| between 2 commits from V\'s local git history.
| When only one commit is given, it is compared to master.
| ".strip_margin()
)
struct Context {
cwd string // current working folder
mut:
vgo vgit.VGitOptions
a string // the full path to the 'after' folder inside workdir
b string // the full path to the 'before' folder inside workdir
vc string // the full path to the vc folder inside workdir. It is used during bootstrapping v from the C source.
commit_before string // the git commit for the 'before' state
commit_after string // the git commit for the 'after' state
warmups int // how many times to execute a command before gathering stats
hyperfineopts string // use for additional CLI options that will be given to the hyperfine command
vflags string // other v options to pass to compared v commands
}
fn new_context() Context {
return Context{
cwd: os.getwd()
commit_after: 'master'
warmups: 4
}
}
fn (c Context) compare_versions() {
// Input is validated at this point...
// Cleanup artifacts from previous runs of this tool:
scripting.chdir(c.vgo.workdir)
scripting.run('rm -rf "$c.a" "$c.b" "$c.vc" ')
// clone the VC source *just once per comparison*, and reuse it:
scripting.run('git clone --quiet "$c.vgo.vc_repo_url" "$c.vc" ')
println('Comparing V performance of commit $c.commit_before (before) vs commit $c.commit_after (after) ...')
c.prepare_v(c.b, c.commit_before)
c.prepare_v(c.a, c.commit_after)
scripting.chdir(c.vgo.workdir)
if c.vflags.len > 0 {
os.setenv('VFLAGS', c.vflags, true)
}
// The first is the baseline, against which all the others will be compared.
// It is the fastest, since hello_world.v has only a single println in it,
mut perf_files := []string{}
perf_files <<
c.compare_v_performance('source_hello', ['vprod @DEBUG@ -o source.c examples/hello_world.v', 'vprod -o source.c examples/hello_world.v', 'v @DEBUG@ -o source.c examples/hello_world.v', 'v -o source.c examples/hello_world.v'])
perf_files <<
c.compare_v_performance('source_v', ['vprod @DEBUG@ -o source.c @COMPILER@', 'vprod -o source.c @COMPILER@', 'v @DEBUG@ -o source.c @COMPILER@', 'v -o source.c @COMPILER@'])
perf_files <<
c.compare_v_performance('binary_hello', ['vprod -o hello examples/hello_world.v', 'v -o hello examples/hello_world.v'])
perf_files <<
c.compare_v_performance('binary_v', ['vprod -o binary @COMPILER@', 'v -o binary @COMPILER@'])
println('All performance files:')
for f in perf_files {
println(' $f')
}
}
fn (c &Context) prepare_v(cdir string, commit string) {
mut cc := os.getenv('CC')
if cc == '' {
cc = 'cc'
}
mut vgit_context := vgit.VGitContext{
cc: cc
commit_v: commit
path_v: cdir
path_vc: c.vc
workdir: c.vgo.workdir
v_repo_url: c.vgo.v_repo_url
vc_repo_url: c.vgo.vc_repo_url
}
vgit_context.compile_oldv_if_needed()
scripting.chdir(cdir)
println('Making a v compiler in $cdir')
scripting.run('./v -cc $cc -o v $vgit_context.vvlocation')
println('Making a vprod compiler in $cdir')
scripting.run('./v -cc $cc -prod -o vprod $vgit_context.vvlocation')
println('Stripping and compressing cv v and vprod binaries in $cdir')
scripting.run('cp cv cv_stripped')
scripting.run('cp v v_stripped')
scripting.run('cp vprod vprod_stripped')
scripting.run('strip *_stripped')
scripting.run('cp cv_stripped cv_stripped_upxed')
scripting.run('cp v_stripped v_stripped_upxed')
scripting.run('cp vprod_stripped vprod_stripped_upxed')
scripting.run('upx -qqq --lzma cv_stripped_upxed')
scripting.run('upx -qqq --lzma v_stripped_upxed')
scripting.run('upx -qqq --lzma vprod_stripped_upxed')
scripting.show_sizes_of_files(['$cdir/cv', '$cdir/cv_stripped', '$cdir/cv_stripped_upxed'])
scripting.show_sizes_of_files(['$cdir/v', '$cdir/v_stripped', '$cdir/v_stripped_upxed'])
scripting.show_sizes_of_files(['$cdir/vprod', '$cdir/vprod_stripped', '$cdir/vprod_stripped_upxed'])
vversion := scripting.run('$cdir/v -version')
vcommit := scripting.run('git rev-parse --short --verify HEAD')
println('V version is: $vversion , local source commit: $vcommit')
if vgit_context.vvlocation == 'cmd/v' {
if os.exists('vlib/v/ast/ast.v') {
println('Source lines of the compiler: ' +
scripting.run('find cmd/v/ vlib/v/ -name "*.v" | grep -v /tests/ | xargs wc | tail -n -1'))
} else {
println('Source lines of the compiler: ' +
scripting.run('wc cmd/v/*.v vlib/compiler/*.v | tail -n -1'))
}
} else if vgit_context.vvlocation == 'v.v' {
println('Source lines of the compiler: ' + scripting.run('wc v.v vlib/compiler/*.v | tail -n -1'))
} else {
println('Source lines of the compiler: ' + scripting.run('wc compiler/*.v | tail -n -1'))
}
}
fn (c Context) compare_v_performance(label string, commands []string) string {
println('---------------------------------------------------------------------------------')
println('Compare v performance when doing the following commands ($label):')
mut source_location_a := ''
mut source_location_b := ''
if os.exists('$c.a/cmd/v') {
source_location_a = 'cmd/v'
} else {
source_location_a = if os.exists('$c.a/v.v') { 'v.v ' } else { 'compiler/ ' }
}
if os.exists('$c.b/cmd/v') {
source_location_b = 'cmd/v'
} else {
source_location_b = if os.exists('$c.b/v.v') { 'v.v ' } else { 'compiler/ ' }
}
timestamp_a, _ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.a/ ; git rev-list -n1 --timestamp HEAD'))
timestamp_b, _ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.b/ ; git rev-list -n1 --timestamp HEAD'))
debug_option_a := if timestamp_a > 1570877641 { '-cg ' } else { '-debug ' }
debug_option_b := if timestamp_b > 1570877641 { '-cg ' } else { '-debug ' }
mut hyperfine_commands_arguments := []string{}
for cmd in commands {
println(cmd)
}
for cmd in commands {
hyperfine_commands_arguments <<
" \'cd ${c.b:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_b, '@DEBUG@', debug_option_b])
}
for cmd in commands {
hyperfine_commands_arguments <<
" \'cd ${c.a:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_a, '@DEBUG@', debug_option_a])
}
// /////////////////////////////////////////////////////////////////////////////
cmd_stats_file := os.real_path([c.vgo.workdir, 'v_performance_stats_${label}.json'].join(os.path_separator))
comparison_cmd := 'hyperfine $c.hyperfineopts ' + '--export-json $cmd_stats_file ' + '--time-unit millisecond ' +
'--style full --warmup $c.warmups ' + hyperfine_commands_arguments.join(' ')
// /////////////////////////////////////////////////////////////////////////////
if c.vgo.verbose {
println(comparison_cmd)
}
os.system(comparison_cmd)
println('The detailed performance comparison report was saved to: $cmd_stats_file .')
println('')
return cmd_stats_file
}
fn main() {
scripting.used_tools_must_exist(['cp', 'rm', 'strip', 'make', 'git', 'upx', 'cc', 'wc', 'tail',
'find', 'xargs', 'hyperfine'])
mut context := new_context()
mut fp := flag.new_flag_parser(os.args)
fp.application(os.file_name(os.executable()))
fp.version(tool_version)
fp.description(tool_description)
fp.arguments_description('COMMIT_BEFORE [COMMIT_AFTER]')
fp.skip_executable()
fp.limit_free_args(1, 2)
context.vflags = fp.string('vflags', 0, '', 'Additional options to pass to the v commands, for example "-cc tcc"')
context.hyperfineopts = fp.string('hyperfine_options', 0, '', 'Additional options passed to hyperfine.
${flag.space}For example on linux, you may want to pass:
$flag.space--hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'"
')
commits := vgit.add_common_tool_options(mut context.vgo, mut fp)
context.commit_before = commits[0]
if commits.len > 1 {
context.commit_after = commits[1]
}
context.b = vgit.normalized_workpath_for_commit(context.vgo.workdir, context.commit_before)
context.a = vgit.normalized_workpath_for_commit(context.vgo.workdir, context.commit_after)
context.vc = vgit.normalized_workpath_for_commit(context.vgo.workdir, 'vc')
if !os.is_dir(context.vgo.workdir) {
msg := 'Work folder: ' + context.vgo.workdir + ' , does not exist.'
eprintln(msg)
exit(2)
}
context.compare_versions()
}