1 | import os |
2 | import flag |
3 | import scripting |
4 | import vgit |
5 | |
6 | const ( |
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 | |
14 | struct Context { |
15 | cwd string // current working folder |
16 | mut: |
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 | |
28 | fn new_context() Context { |
29 | return Context{ |
30 | cwd: os.getwd() |
31 | commit_after: 'master' |
32 | warmups: 4 |
33 | } |
34 | } |
35 | |
36 | fn (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 | |
79 | fn (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 | |
134 | fn (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 | |
189 | fn 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 | } |