import os import flag import scripting import vgit const ( tool_version = '0.0.6' 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 --filter=blob:none --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) scripting.run('${cdir}/v version') 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')) // 1570877641 is 065ce39 2019-10-12 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() { // allow for `v run cmd/tools/performance_compare.v`, see oldv.v os.setenv('VEXE', '', true) 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) { eprintln('Work folder: `{context.vgo.workdir}` , does not exist. Use `--workdir /some/path` to set it.') exit(2) } context.compare_versions() }