xref: /aosp_15_r20/external/bc/scripts/karatsuba.py (revision 5a6e848804d15c18a0125914844ee4eb0bda4fcf)
1*5a6e8488SAndroid Build Coastguard Worker#! /usr/bin/python3 -B
2*5a6e8488SAndroid Build Coastguard Worker#
3*5a6e8488SAndroid Build Coastguard Worker# SPDX-License-Identifier: BSD-2-Clause
4*5a6e8488SAndroid Build Coastguard Worker#
5*5a6e8488SAndroid Build Coastguard Worker# Copyright (c) 2018-2024 Gavin D. Howard and contributors.
6*5a6e8488SAndroid Build Coastguard Worker#
7*5a6e8488SAndroid Build Coastguard Worker# Redistribution and use in source and binary forms, with or without
8*5a6e8488SAndroid Build Coastguard Worker# modification, are permitted provided that the following conditions are met:
9*5a6e8488SAndroid Build Coastguard Worker#
10*5a6e8488SAndroid Build Coastguard Worker# * Redistributions of source code must retain the above copyright notice, this
11*5a6e8488SAndroid Build Coastguard Worker#   list of conditions and the following disclaimer.
12*5a6e8488SAndroid Build Coastguard Worker#
13*5a6e8488SAndroid Build Coastguard Worker# * Redistributions in binary form must reproduce the above copyright notice,
14*5a6e8488SAndroid Build Coastguard Worker#   this list of conditions and the following disclaimer in the documentation
15*5a6e8488SAndroid Build Coastguard Worker#   and/or other materials provided with the distribution.
16*5a6e8488SAndroid Build Coastguard Worker#
17*5a6e8488SAndroid Build Coastguard Worker# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18*5a6e8488SAndroid Build Coastguard Worker# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19*5a6e8488SAndroid Build Coastguard Worker# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20*5a6e8488SAndroid Build Coastguard Worker# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21*5a6e8488SAndroid Build Coastguard Worker# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22*5a6e8488SAndroid Build Coastguard Worker# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23*5a6e8488SAndroid Build Coastguard Worker# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24*5a6e8488SAndroid Build Coastguard Worker# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25*5a6e8488SAndroid Build Coastguard Worker# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26*5a6e8488SAndroid Build Coastguard Worker# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27*5a6e8488SAndroid Build Coastguard Worker# POSSIBILITY OF SUCH DAMAGE.
28*5a6e8488SAndroid Build Coastguard Worker#
29*5a6e8488SAndroid Build Coastguard Worker
30*5a6e8488SAndroid Build Coastguard Workerimport os
31*5a6e8488SAndroid Build Coastguard Workerimport sys
32*5a6e8488SAndroid Build Coastguard Workerimport subprocess
33*5a6e8488SAndroid Build Coastguard Workerimport time
34*5a6e8488SAndroid Build Coastguard Worker
35*5a6e8488SAndroid Build Coastguard Worker# Print the usage and exit with an error.
36*5a6e8488SAndroid Build Coastguard Workerdef usage():
37*5a6e8488SAndroid Build Coastguard Worker	print("usage: {} [num_iterations test_num exe]".format(script))
38*5a6e8488SAndroid Build Coastguard Worker	print("\n    num_iterations is the number of times to run each karatsuba number; default is 4")
39*5a6e8488SAndroid Build Coastguard Worker	print("\n    test_num is the last Karatsuba number to run through tests")
40*5a6e8488SAndroid Build Coastguard Worker	sys.exit(1)
41*5a6e8488SAndroid Build Coastguard Worker
42*5a6e8488SAndroid Build Coastguard Worker# Run a command. This is basically an alias.
43*5a6e8488SAndroid Build Coastguard Workerdef run(cmd, env=None):
44*5a6e8488SAndroid Build Coastguard Worker	return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
45*5a6e8488SAndroid Build Coastguard Worker
46*5a6e8488SAndroid Build Coastguard Workerscript = sys.argv[0]
47*5a6e8488SAndroid Build Coastguard Workertestdir = os.path.dirname(script)
48*5a6e8488SAndroid Build Coastguard Worker
49*5a6e8488SAndroid Build Coastguard Workerif testdir == "":
50*5a6e8488SAndroid Build Coastguard Worker	testdir = os.getcwd()
51*5a6e8488SAndroid Build Coastguard Worker
52*5a6e8488SAndroid Build Coastguard Workerprint("\nWARNING: This script is for distro and package maintainers.")
53*5a6e8488SAndroid Build Coastguard Workerprint("It is for finding the optimal Karatsuba number.")
54*5a6e8488SAndroid Build Coastguard Workerprint("Though it only needs to be run once per release/platform,")
55*5a6e8488SAndroid Build Coastguard Workerprint("it takes forever to run.")
56*5a6e8488SAndroid Build Coastguard Workerprint("You have been warned.\n")
57*5a6e8488SAndroid Build Coastguard Workerprint("Note: If you send an interrupt, it will report the current best number.\n")
58*5a6e8488SAndroid Build Coastguard Worker
59*5a6e8488SAndroid Build Coastguard Worker# This script has to be run by itself.
60*5a6e8488SAndroid Build Coastguard Workerif __name__ != "__main__":
61*5a6e8488SAndroid Build Coastguard Worker	usage()
62*5a6e8488SAndroid Build Coastguard Worker
63*5a6e8488SAndroid Build Coastguard Worker# These constants can be changed, but I found they work well enough.
64*5a6e8488SAndroid Build Coastguard Workermx = 520
65*5a6e8488SAndroid Build Coastguard Workermx2 = mx // 2
66*5a6e8488SAndroid Build Coastguard Workermn = 16
67*5a6e8488SAndroid Build Coastguard Worker
68*5a6e8488SAndroid Build Coastguard Workernum = "9" * mx
69*5a6e8488SAndroid Build Coastguard Worker
70*5a6e8488SAndroid Build Coastguard Workerargs_idx = 4
71*5a6e8488SAndroid Build Coastguard Worker
72*5a6e8488SAndroid Build Coastguard Worker# Command-line processing.
73*5a6e8488SAndroid Build Coastguard Workerif len(sys.argv) >= 2:
74*5a6e8488SAndroid Build Coastguard Worker	num_iterations = int(sys.argv[1])
75*5a6e8488SAndroid Build Coastguard Workerelse:
76*5a6e8488SAndroid Build Coastguard Worker	num_iterations = 4
77*5a6e8488SAndroid Build Coastguard Worker
78*5a6e8488SAndroid Build Coastguard Workerif len(sys.argv) >= 3:
79*5a6e8488SAndroid Build Coastguard Worker	test_num = int(sys.argv[2])
80*5a6e8488SAndroid Build Coastguard Workerelse:
81*5a6e8488SAndroid Build Coastguard Worker	test_num = 0
82*5a6e8488SAndroid Build Coastguard Worker
83*5a6e8488SAndroid Build Coastguard Workerif len(sys.argv) >= args_idx:
84*5a6e8488SAndroid Build Coastguard Worker	exe = sys.argv[3]
85*5a6e8488SAndroid Build Coastguard Workerelse:
86*5a6e8488SAndroid Build Coastguard Worker	exe = testdir + "/bin/bc"
87*5a6e8488SAndroid Build Coastguard Worker
88*5a6e8488SAndroid Build Coastguard Workerexedir = os.path.dirname(exe)
89*5a6e8488SAndroid Build Coastguard Worker
90*5a6e8488SAndroid Build Coastguard Worker# Some basic tests.
91*5a6e8488SAndroid Build Coastguard Workerindata = "for (i = 0; i < 100; ++i) {} * {}\n"
92*5a6e8488SAndroid Build Coastguard Workerindata += "1.23456789^100000\n1.23456789^100000\nhalt"
93*5a6e8488SAndroid Build Coastguard Workerindata = indata.format(num, num).encode()
94*5a6e8488SAndroid Build Coastguard Worker
95*5a6e8488SAndroid Build Coastguard Workertimes = []
96*5a6e8488SAndroid Build Coastguard Workernums = []
97*5a6e8488SAndroid Build Coastguard Workerruns = []
98*5a6e8488SAndroid Build Coastguard Workernruns = num_iterations + 1
99*5a6e8488SAndroid Build Coastguard Worker
100*5a6e8488SAndroid Build Coastguard Worker# We build the list first because I want to just edit slots.
101*5a6e8488SAndroid Build Coastguard Workerfor i in range(0, nruns):
102*5a6e8488SAndroid Build Coastguard Worker	runs.append(0)
103*5a6e8488SAndroid Build Coastguard Worker
104*5a6e8488SAndroid Build Coastguard Workertests = [ "multiply", "modulus", "power", "sqrt" ]
105*5a6e8488SAndroid Build Coastguard Workerscripts = [ "multiply" ]
106*5a6e8488SAndroid Build Coastguard Worker
107*5a6e8488SAndroid Build Coastguard Worker# Test Link-Time Optimization.
108*5a6e8488SAndroid Build Coastguard Workerprint("Testing CFLAGS=\"-flto\"...")
109*5a6e8488SAndroid Build Coastguard Worker
110*5a6e8488SAndroid Build Coastguard Workerflags = dict(os.environ)
111*5a6e8488SAndroid Build Coastguard Workertry:
112*5a6e8488SAndroid Build Coastguard Worker	flags["CFLAGS"] = flags["CFLAGS"] + " " + "-flto"
113*5a6e8488SAndroid Build Coastguard Workerexcept KeyError:
114*5a6e8488SAndroid Build Coastguard Worker	flags["CFLAGS"] = "-flto"
115*5a6e8488SAndroid Build Coastguard Worker
116*5a6e8488SAndroid Build Coastguard Workerp = run([ "{}/../configure.sh".format(testdir), "-O3" ], flags)
117*5a6e8488SAndroid Build Coastguard Workerif p.returncode != 0:
118*5a6e8488SAndroid Build Coastguard Worker	print("configure.sh returned an error ({}); exiting...".format(p.returncode))
119*5a6e8488SAndroid Build Coastguard Worker	sys.exit(p.returncode)
120*5a6e8488SAndroid Build Coastguard Worker
121*5a6e8488SAndroid Build Coastguard Workerp = run([ "make" ])
122*5a6e8488SAndroid Build Coastguard Worker
123*5a6e8488SAndroid Build Coastguard Workerif p.returncode == 0:
124*5a6e8488SAndroid Build Coastguard Worker	config_env = flags
125*5a6e8488SAndroid Build Coastguard Worker	print("Using CFLAGS=\"-flto\"")
126*5a6e8488SAndroid Build Coastguard Workerelse:
127*5a6e8488SAndroid Build Coastguard Worker	config_env = os.environ
128*5a6e8488SAndroid Build Coastguard Worker	print("Not using CFLAGS=\"-flto\"")
129*5a6e8488SAndroid Build Coastguard Worker
130*5a6e8488SAndroid Build Coastguard Workerp = run([ "make", "clean" ])
131*5a6e8488SAndroid Build Coastguard Worker
132*5a6e8488SAndroid Build Coastguard Worker# Test parallel build. My machine has 16 cores.
133*5a6e8488SAndroid Build Coastguard Workerprint("Testing \"make -j16\"")
134*5a6e8488SAndroid Build Coastguard Worker
135*5a6e8488SAndroid Build Coastguard Workerif p.returncode != 0:
136*5a6e8488SAndroid Build Coastguard Worker	print("make returned an error ({}); exiting...".format(p.returncode))
137*5a6e8488SAndroid Build Coastguard Worker	sys.exit(p.returncode)
138*5a6e8488SAndroid Build Coastguard Worker
139*5a6e8488SAndroid Build Coastguard Workerp = run([ "make", "-j16" ])
140*5a6e8488SAndroid Build Coastguard Worker
141*5a6e8488SAndroid Build Coastguard Workerif p.returncode == 0:
142*5a6e8488SAndroid Build Coastguard Worker	makecmd = [ "make", "-j16" ]
143*5a6e8488SAndroid Build Coastguard Worker	print("Using \"make -j16\"")
144*5a6e8488SAndroid Build Coastguard Workerelse:
145*5a6e8488SAndroid Build Coastguard Worker	makecmd = [ "make" ]
146*5a6e8488SAndroid Build Coastguard Worker	print("Not using \"make -j16\"")
147*5a6e8488SAndroid Build Coastguard Worker
148*5a6e8488SAndroid Build Coastguard Worker# Set the max if the user did.
149*5a6e8488SAndroid Build Coastguard Workerif test_num != 0:
150*5a6e8488SAndroid Build Coastguard Worker	mx2 = test_num
151*5a6e8488SAndroid Build Coastguard Worker
152*5a6e8488SAndroid Build Coastguard Worker# This is the meat here.
153*5a6e8488SAndroid Build Coastguard Workertry:
154*5a6e8488SAndroid Build Coastguard Worker
155*5a6e8488SAndroid Build Coastguard Worker	# For each possible KARATSUBA_LEN...
156*5a6e8488SAndroid Build Coastguard Worker	for i in range(mn, mx2 + 1):
157*5a6e8488SAndroid Build Coastguard Worker
158*5a6e8488SAndroid Build Coastguard Worker		# Configure and compile.
159*5a6e8488SAndroid Build Coastguard Worker		print("\nCompiling...\n")
160*5a6e8488SAndroid Build Coastguard Worker
161*5a6e8488SAndroid Build Coastguard Worker		p = run([ "{}/../configure.sh".format(testdir), "-O3", "-k{}".format(i) ], config_env)
162*5a6e8488SAndroid Build Coastguard Worker
163*5a6e8488SAndroid Build Coastguard Worker		if p.returncode != 0:
164*5a6e8488SAndroid Build Coastguard Worker			print("configure.sh returned an error ({}); exiting...".format(p.returncode))
165*5a6e8488SAndroid Build Coastguard Worker			sys.exit(p.returncode)
166*5a6e8488SAndroid Build Coastguard Worker
167*5a6e8488SAndroid Build Coastguard Worker		p = run(makecmd)
168*5a6e8488SAndroid Build Coastguard Worker
169*5a6e8488SAndroid Build Coastguard Worker		if p.returncode != 0:
170*5a6e8488SAndroid Build Coastguard Worker			print("make returned an error ({}); exiting...".format(p.returncode))
171*5a6e8488SAndroid Build Coastguard Worker			sys.exit(p.returncode)
172*5a6e8488SAndroid Build Coastguard Worker
173*5a6e8488SAndroid Build Coastguard Worker		# Test if desired.
174*5a6e8488SAndroid Build Coastguard Worker		if (test_num >= i):
175*5a6e8488SAndroid Build Coastguard Worker
176*5a6e8488SAndroid Build Coastguard Worker			print("Running tests for Karatsuba Num: {}\n".format(i))
177*5a6e8488SAndroid Build Coastguard Worker
178*5a6e8488SAndroid Build Coastguard Worker			for test in tests:
179*5a6e8488SAndroid Build Coastguard Worker
180*5a6e8488SAndroid Build Coastguard Worker				cmd = [ "{}/../tests/test.sh".format(testdir), "bc", test, "0", "0", exe ]
181*5a6e8488SAndroid Build Coastguard Worker
182*5a6e8488SAndroid Build Coastguard Worker				p = subprocess.run(cmd + sys.argv[args_idx:], stderr=subprocess.PIPE)
183*5a6e8488SAndroid Build Coastguard Worker
184*5a6e8488SAndroid Build Coastguard Worker				if p.returncode != 0:
185*5a6e8488SAndroid Build Coastguard Worker					print("{} test failed:\n".format(test, p.returncode))
186*5a6e8488SAndroid Build Coastguard Worker					print(p.stderr.decode())
187*5a6e8488SAndroid Build Coastguard Worker					print("\nexiting...")
188*5a6e8488SAndroid Build Coastguard Worker					sys.exit(p.returncode)
189*5a6e8488SAndroid Build Coastguard Worker
190*5a6e8488SAndroid Build Coastguard Worker			print("")
191*5a6e8488SAndroid Build Coastguard Worker
192*5a6e8488SAndroid Build Coastguard Worker			for script in scripts:
193*5a6e8488SAndroid Build Coastguard Worker
194*5a6e8488SAndroid Build Coastguard Worker				cmd = [ "{}/../tests/script.sh".format(testdir), "bc", script + ".bc",
195*5a6e8488SAndroid Build Coastguard Worker				        "0", "1", "1", "0", exe ]
196*5a6e8488SAndroid Build Coastguard Worker
197*5a6e8488SAndroid Build Coastguard Worker				p = subprocess.run(cmd + sys.argv[args_idx:], stderr=subprocess.PIPE)
198*5a6e8488SAndroid Build Coastguard Worker
199*5a6e8488SAndroid Build Coastguard Worker				if p.returncode != 0:
200*5a6e8488SAndroid Build Coastguard Worker					print("{} test failed:\n".format(test, p.returncode))
201*5a6e8488SAndroid Build Coastguard Worker					print(p.stderr.decode())
202*5a6e8488SAndroid Build Coastguard Worker					print("\nexiting...")
203*5a6e8488SAndroid Build Coastguard Worker					sys.exit(p.returncode)
204*5a6e8488SAndroid Build Coastguard Worker
205*5a6e8488SAndroid Build Coastguard Worker			print("")
206*5a6e8488SAndroid Build Coastguard Worker
207*5a6e8488SAndroid Build Coastguard Worker		# If testing was *not* desired, assume the user wanted to time it.
208*5a6e8488SAndroid Build Coastguard Worker		elif test_num == 0:
209*5a6e8488SAndroid Build Coastguard Worker
210*5a6e8488SAndroid Build Coastguard Worker			print("Timing Karatsuba Num: {}".format(i), end='', flush=True)
211*5a6e8488SAndroid Build Coastguard Worker
212*5a6e8488SAndroid Build Coastguard Worker			for j in range(0, nruns):
213*5a6e8488SAndroid Build Coastguard Worker
214*5a6e8488SAndroid Build Coastguard Worker				cmd = [ exe, "{}/../tests/bc/power.txt".format(testdir) ]
215*5a6e8488SAndroid Build Coastguard Worker
216*5a6e8488SAndroid Build Coastguard Worker				start = time.perf_counter()
217*5a6e8488SAndroid Build Coastguard Worker				p = subprocess.run(cmd, input=indata, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
218*5a6e8488SAndroid Build Coastguard Worker				end = time.perf_counter()
219*5a6e8488SAndroid Build Coastguard Worker
220*5a6e8488SAndroid Build Coastguard Worker				if p.returncode != 0:
221*5a6e8488SAndroid Build Coastguard Worker					print("bc returned an error; exiting...")
222*5a6e8488SAndroid Build Coastguard Worker					sys.exit(p.returncode)
223*5a6e8488SAndroid Build Coastguard Worker
224*5a6e8488SAndroid Build Coastguard Worker				runs[j] = end - start
225*5a6e8488SAndroid Build Coastguard Worker
226*5a6e8488SAndroid Build Coastguard Worker			run_times = runs[1:]
227*5a6e8488SAndroid Build Coastguard Worker			avg = sum(run_times) / len(run_times)
228*5a6e8488SAndroid Build Coastguard Worker
229*5a6e8488SAndroid Build Coastguard Worker			times.append(avg)
230*5a6e8488SAndroid Build Coastguard Worker			nums.append(i)
231*5a6e8488SAndroid Build Coastguard Worker			print(", Time: {}".format(times[i - mn]))
232*5a6e8488SAndroid Build Coastguard Worker
233*5a6e8488SAndroid Build Coastguard Workerexcept KeyboardInterrupt:
234*5a6e8488SAndroid Build Coastguard Worker	# When timing, we want to quit when the user tells us to. However, we also
235*5a6e8488SAndroid Build Coastguard Worker	# want to report the best run, so we make sure to grab the times here before
236*5a6e8488SAndroid Build Coastguard Worker	# moving on.
237*5a6e8488SAndroid Build Coastguard Worker	nums = nums[0:i]
238*5a6e8488SAndroid Build Coastguard Worker	times = times[0:i]
239*5a6e8488SAndroid Build Coastguard Worker
240*5a6e8488SAndroid Build Coastguard Worker# If running timed tests...
241*5a6e8488SAndroid Build Coastguard Workerif test_num == 0:
242*5a6e8488SAndroid Build Coastguard Worker
243*5a6e8488SAndroid Build Coastguard Worker	# Report the optimal KARATSUBA_LEN
244*5a6e8488SAndroid Build Coastguard Worker	opt = nums[times.index(min(times))]
245*5a6e8488SAndroid Build Coastguard Worker
246*5a6e8488SAndroid Build Coastguard Worker	print("\n\nOptimal Karatsuba Num (for this machine): {}".format(opt))
247*5a6e8488SAndroid Build Coastguard Worker	print("Run the following:\n")
248*5a6e8488SAndroid Build Coastguard Worker	if "-flto" in config_env["CFLAGS"]:
249*5a6e8488SAndroid Build Coastguard Worker		print("CFLAGS=\"-flto\" ./configure.sh -O3 -k {}".format(opt))
250*5a6e8488SAndroid Build Coastguard Worker	else:
251*5a6e8488SAndroid Build Coastguard Worker		print("./configure.sh -O3 -k {}".format(opt))
252*5a6e8488SAndroid Build Coastguard Worker	print("make")
253