v / cmd / tools
Raw file | 220 loc (213 sloc) | 8.49 KB | Latest commit hash 017ace6ea
1import os
2import flag
3import scripting
4import vgit
5
6const (
7 tool_version = '0.0.6'
8 tool_description = " Compares V executable size and performance,
9| between 2 commits from V's local git history.
10| When only one commit is given, it is compared to master.
11| ".strip_margin()
12)
13
14struct Context {
15 cwd string // current working folder
16mut:
17 vgo vgit.VGitOptions
18 a string // the full path to the 'after' folder inside workdir
19 b string // the full path to the 'before' folder inside workdir
20 vc string // the full path to the vc folder inside workdir. It is used during bootstrapping v from the C source.
21 commit_before string // the git commit for the 'before' state
22 commit_after string // the git commit for the 'after' state
23 warmups int // how many times to execute a command before gathering stats
24 hyperfineopts string // use for additional CLI options that will be given to the hyperfine command
25 vflags string // other v options to pass to compared v commands
26}
27
28fn new_context() Context {
29 return Context{
30 cwd: os.getwd()
31 commit_after: 'master'
32 warmups: 4
33 }
34}
35
36fn (c Context) compare_versions() {
37 // Input is validated at this point...
38 // Cleanup artifacts from previous runs of this tool:
39 scripting.chdir(c.vgo.workdir)
40 scripting.run('rm -rf "${c.a}" "${c.b}" "${c.vc}" ')
41 // clone the VC source *just once per comparison*, and reuse it:
42 scripting.run('git clone --filter=blob:none --quiet "${c.vgo.vc_repo_url}" "${c.vc}" ')
43 println('Comparing V performance of commit ${c.commit_before} (before) vs commit ${c.commit_after} (after) ...')
44 c.prepare_v(c.b, c.commit_before)
45 c.prepare_v(c.a, c.commit_after)
46 scripting.chdir(c.vgo.workdir)
47 if c.vflags.len > 0 {
48 os.setenv('VFLAGS', c.vflags, true)
49 }
50 // The first is the baseline, against which all the others will be compared.
51 // It is the fastest, since hello_world.v has only a single println in it,
52 mut perf_files := []string{}
53 perf_files << c.compare_v_performance('source_hello', [
54 'vprod @DEBUG@ -o source.c examples/hello_world.v',
55 'vprod -o source.c examples/hello_world.v',
56 'v @DEBUG@ -o source.c examples/hello_world.v',
57 'v -o source.c examples/hello_world.v',
58 ])
59 perf_files << c.compare_v_performance('source_v', [
60 'vprod @DEBUG@ -o source.c @COMPILER@',
61 'vprod -o source.c @COMPILER@',
62 'v @DEBUG@ -o source.c @COMPILER@',
63 'v -o source.c @COMPILER@',
64 ])
65 perf_files << c.compare_v_performance('binary_hello', [
66 'vprod -o hello examples/hello_world.v',
67 'v -o hello examples/hello_world.v',
68 ])
69 perf_files << c.compare_v_performance('binary_v', [
70 'vprod -o binary @COMPILER@',
71 'v -o binary @COMPILER@',
72 ])
73 println('All performance files:')
74 for f in perf_files {
75 println(' ${f}')
76 }
77}
78
79fn (c &Context) prepare_v(cdir string, commit string) {
80 mut cc := os.getenv('CC')
81 if cc == '' {
82 cc = 'cc'
83 }
84 mut vgit_context := vgit.VGitContext{
85 cc: cc
86 commit_v: commit
87 path_v: cdir
88 path_vc: c.vc
89 workdir: c.vgo.workdir
90 v_repo_url: c.vgo.v_repo_url
91 vc_repo_url: c.vgo.vc_repo_url
92 }
93 vgit_context.compile_oldv_if_needed()
94 scripting.chdir(cdir)
95 scripting.run('${cdir}/v version')
96 println('Making a v compiler in ${cdir}')
97 scripting.run('./v -cc ${cc} -o v ${vgit_context.vvlocation}')
98 println('Making a vprod compiler in ${cdir}')
99 scripting.run('./v -cc ${cc} -prod -o vprod ${vgit_context.vvlocation}')
100 println('Stripping and compressing cv v and vprod binaries in ${cdir}')
101 scripting.run('cp cv cv_stripped')
102 scripting.run('cp v v_stripped')
103 scripting.run('cp vprod vprod_stripped')
104 scripting.run('strip *_stripped')
105 scripting.run('cp cv_stripped cv_stripped_upxed')
106 scripting.run('cp v_stripped v_stripped_upxed')
107 scripting.run('cp vprod_stripped vprod_stripped_upxed')
108 scripting.run('upx -qqq --lzma cv_stripped_upxed')
109 scripting.run('upx -qqq --lzma v_stripped_upxed')
110 scripting.run('upx -qqq --lzma vprod_stripped_upxed')
111 scripting.show_sizes_of_files(['${cdir}/cv', '${cdir}/cv_stripped', '${cdir}/cv_stripped_upxed'])
112 scripting.show_sizes_of_files(['${cdir}/v', '${cdir}/v_stripped', '${cdir}/v_stripped_upxed'])
113 scripting.show_sizes_of_files(['${cdir}/vprod', '${cdir}/vprod_stripped',
114 '${cdir}/vprod_stripped_upxed'])
115 vversion := scripting.run('${cdir}/v -version')
116 vcommit := scripting.run('git rev-parse --short --verify HEAD')
117 println('V version is: ${vversion} , local source commit: ${vcommit}')
118 if vgit_context.vvlocation == 'cmd/v' {
119 if os.exists('vlib/v/ast/ast.v') {
120 println('Source lines of the compiler: ' +
121 scripting.run('find cmd/v/ vlib/v/ -name "*.v" | grep -v /tests/ | xargs wc | tail -n -1'))
122 } else {
123 println('Source lines of the compiler: ' +
124 scripting.run('wc cmd/v/*.v vlib/compiler/*.v | tail -n -1'))
125 }
126 } else if vgit_context.vvlocation == 'v.v' {
127 println('Source lines of the compiler: ' +
128 scripting.run('wc v.v vlib/compiler/*.v | tail -n -1'))
129 } else {
130 println('Source lines of the compiler: ' + scripting.run('wc compiler/*.v | tail -n -1'))
131 }
132}
133
134fn (c Context) compare_v_performance(label string, commands []string) string {
135 println('---------------------------------------------------------------------------------')
136 println('Compare v performance when doing the following commands (${label}):')
137 mut source_location_a := ''
138 mut source_location_b := ''
139 if os.exists('${c.a}/cmd/v') {
140 source_location_a = 'cmd/v'
141 } else {
142 source_location_a = if os.exists('${c.a}/v.v') { 'v.v ' } else { 'compiler/ ' }
143 }
144 if os.exists('${c.b}/cmd/v') {
145 source_location_b = 'cmd/v'
146 } else {
147 source_location_b = if os.exists('${c.b}/v.v') { 'v.v ' } else { 'compiler/ ' }
148 }
149 timestamp_a, _ := vgit.line_to_timestamp_and_commit(scripting.run('cd ${c.a}/ ; git rev-list -n1 --timestamp HEAD'))
150 timestamp_b, _ := vgit.line_to_timestamp_and_commit(scripting.run('cd ${c.b}/ ; git rev-list -n1 --timestamp HEAD'))
151 // 1570877641 is 065ce39 2019-10-12
152 debug_option_a := if timestamp_a > 1570877641 { '-cg ' } else { '-debug ' }
153 debug_option_b := if timestamp_b > 1570877641 { '-cg ' } else { '-debug ' }
154 mut hyperfine_commands_arguments := []string{}
155 for cmd in commands {
156 println(cmd)
157 }
158 for cmd in commands {
159 hyperfine_commands_arguments << ' \'cd ${c.b:-34s} ; ./${cmd} \' '.replace_each([
160 '@COMPILER@',
161 source_location_b,
162 '@DEBUG@',
163 debug_option_b,
164 ])
165 }
166 for cmd in commands {
167 hyperfine_commands_arguments << ' \'cd ${c.a:-34s} ; ./${cmd} \' '.replace_each([
168 '@COMPILER@',
169 source_location_a,
170 '@DEBUG@',
171 debug_option_a,
172 ])
173 }
174 // /////////////////////////////////////////////////////////////////////////////
175 cmd_stats_file := os.real_path([c.vgo.workdir, 'v_performance_stats_${label}.json'].join(os.path_separator))
176 comparison_cmd := 'hyperfine ${c.hyperfineopts} ' + '--export-json ${cmd_stats_file} ' +
177 '--time-unit millisecond ' + '--style full --warmup ${c.warmups} ' +
178 hyperfine_commands_arguments.join(' ')
179 // /////////////////////////////////////////////////////////////////////////////
180 if c.vgo.verbose {
181 println(comparison_cmd)
182 }
183 os.system(comparison_cmd)
184 println('The detailed performance comparison report was saved to: ${cmd_stats_file} .')
185 println('')
186 return cmd_stats_file
187}
188
189fn main() {
190 // allow for `v run cmd/tools/performance_compare.v`, see oldv.v
191 os.setenv('VEXE', '', true)
192 scripting.used_tools_must_exist(['cp', 'rm', 'strip', 'make', 'git', 'upx', 'cc', 'wc', 'tail',
193 'find', 'xargs', 'hyperfine'])
194 mut context := new_context()
195 mut fp := flag.new_flag_parser(os.args)
196 fp.application(os.file_name(os.executable()))
197 fp.version(tool_version)
198 fp.description(tool_description)
199 fp.arguments_description('COMMIT_BEFORE [COMMIT_AFTER]')
200 fp.skip_executable()
201 fp.limit_free_args(1, 2)!
202 context.vflags = fp.string('vflags', 0, '', 'Additional options to pass to the v commands, for example "-cc tcc"')
203 context.hyperfineopts = fp.string('hyperfine_options', 0, '', 'Additional options passed to hyperfine.
204${flag.space}For example on linux, you may want to pass:
205${flag.space}--hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'"
206')
207 commits := vgit.add_common_tool_options(mut context.vgo, mut fp)
208 context.commit_before = commits[0]
209 if commits.len > 1 {
210 context.commit_after = commits[1]
211 }
212 context.b = vgit.normalized_workpath_for_commit(context.vgo.workdir, context.commit_before)
213 context.a = vgit.normalized_workpath_for_commit(context.vgo.workdir, context.commit_after)
214 context.vc = vgit.normalized_workpath_for_commit(context.vgo.workdir, 'vc')
215 if !os.is_dir(context.vgo.workdir) {
216 eprintln('Work folder: `{context.vgo.workdir}` , does not exist. Use `--workdir /some/path` to set it.')
217 exit(2)
218 }
219 context.compare_versions()
220}