-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreorder.py
258 lines (208 loc) · 7.66 KB
/
reorder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
from __future__ import division
from util import *
import signal
import collections
import functools
import math
import autotune
import random
import multiprocessing
import multiprocess
from config import config
import numpy
import re
from copy import copy
from concurrent.futures import ThreadPoolExecutor
class Transform(object):
@classmethod
def new(cls, module, job):
'''
return a new transformation that does nothing
'''
raise NotImplementedError
def mutate(self):
'''
return a slightly mutated mutation
'''
raise NotImplementedError
def apply(self):
'''
apply transformation to current module
'''
raise NotImplementedError
def update_module(self):
'''
update the module with `self.transformation`
after this, `self.apply` should be a nop
this is so that Reordering transformation can maintain an intermediate module
subclass doesn't have to implement this method
'''
pass
def compile(self):
'''
apply a transformation to module, compile the transformed module, and
return name of of the executable file
'''
transformed = self.apply()
obj = compile_module(transformed)
exe = get_temp()
self.job.make(exe, OBJ=obj, EXE=exe)
delete_temp(obj)
delete_temp(transformed)
return exe
def evaluate(self):
'''
evaluate cost of the transformation
'''
exe = self.compile()
r = self.job.run(exe)
delete_temp(exe)
return r.elapsed
CodeLayoutTransform = collections.namedtuple('CodeLayoutTransform', ['kind', 'func1', 'func2'])
def transform2args(transform):
'''
turn a CodeLayoutTransform into command line argument for bin/reorer-functions
'''
return '-t{0},{1},{2}'.format(transform.kind, transform.func1, transform.func2)
# TODO what happens when there's only one function (or two...) in the module?
class Reordering(Transform):
# (<functions>, <delarations>)
module_info = None
# probability to reorder functions
p_funcs = 0.8
p_shuffle = 0.01
p_swap = 0.2
def __init__(self, reorderings, module, job, using_temp=False):
self.reorderings = reorderings
self.module = module
self.job = job
self.using_temp = using_temp
@classmethod
def get_module_info(cls, module):
'''
return { function : number of basic blocks },
list of each function's probabitlity to be chosen to reorder its basic blocks
'''
if cls.module_info is None:
r = call('{tunerpath}/bin/reorder-functions -list-functions {m}'.format(
tunerpath=config.tunerpath,
m=module))
s = r.stdout.split('\n')[0]
info = {}
probs = []
total_weight = 0
for pair in s.split(','):
func, bb_count_number = pair.split('|')
info[func] = bb_count = int(bb_count_number)
weight = 0 if bb_count <= 2 else bb_count
probs.append(weight)
total_weight += weight
for i, weight in enumerate(probs):
probs[i] = weight / total_weight
cls.module_info = info, probs
return cls.module_info
@classmethod
def rand_reordering(cls, module):
reorderings = []
if random.random() < cls.p_funcs:
reorderings += cls.reorder_functions(module)
reorderings += cls.reorder_basic_blocks(module)
return reorderings
@classmethod
def reorder_basic_blocks(cls, module):
module_info, chosen_probs = cls.get_module_info(module)
func = numpy.random.choice(module_info.keys(), p=chosen_probs)
num_bbs = module_info[func]
reorderings = []
# with some probability swap a basic blocks with the next one
for i in xrange(1, num_bbs-1):
if random.random() < cls.p_swap:
reorderings.append(CodeLayoutTransform('s'+func, str(i), str(i+1)))
if random.random() < cls.p_shuffle:
reorderings.append(CodeLayoutTransform('m'+func, str(i), str(choose(range(1, num_bbs), except_=i))))
return reorderings
@classmethod
def reorder_functions(cls, module):
kind = choose(['s', 'm'])
functions = cls.get_module_info(module)[0].keys()
func1 = choose(functions)
func2 = choose(functions, except_=func1)
return [CodeLayoutTransform(kind, func1, func2)]
@classmethod
def new(cls, module, job):
return Reordering([], module, job)
def mutate(self):
new_reorderings = self.__class__.rand_reordering(self.module)
return Reordering(self.reorderings + new_reorderings, self.module, self.job, self.using_temp)
def as_args(self):
return map(transform2args, self.reorderings)
def apply(self):
transformed = get_temp()
transform_cmd = '{tunerpath}/bin/reorder-functions {old} -o {new} {args}'.format(
tunerpath=config.tunerpath,
old=self.module,
new=transformed,
args=' '.join(self.as_args()))
call(transform_cmd)
return transformed
def update_module(self):
transformed = self.apply()
if self.using_temp:
delete_temp(self.module)
self.module = transformed
self.using_temp = True
self.reorderings = []
default_num_workers = int(max(1, multiprocessing.cpu_count() / 2))
def tune(module, makefile,
obj_var='OBJ',
run_rule='run',
using_server=False,
transform_type=Reordering,
iterations=100,
num_workers=default_num_workers):
job = autotune.TuningJob(makefile, obj_var, run_rule)
init_transform = transform_type.new(module, job)
cost = best_cost = init_cost = init_transform.evaluate()
best = new_transform = transform = init_transform
# hack
best_module = module
in_module = module
# magic
t_max = -0.01 / math.log(0.8)
t_min = -0.01 / math.log(0.0001)
alpha = math.exp(math.log(t_min/t_max) / iterations)
signal.signal(signal.SIGINT, signal.SIG_IGN)
#pool = multiprocess.Pool(num_workers)
pool = ThreadPoolExecutor(num_workers)
try:
i = 0
t = t_max
while i < iterations:
# speculatively evaluate the transformations
# e.g. assume that none of the transformations gets accepted
candidates = [transform.mutate() for _ in xrange(num_workers)]
new_costs = pool.map(lambda t: t.evaluate(), candidates)
skipped = 0
for new_cost in new_costs:
normalize = lambda x: x / new_cost
diff = (normalize(cost) - normalize(new_cost))
new_transform = candidates[skipped]
skipped += 1
if new_cost <= cost or t > t_min and random.random() < math.exp(diff / t):
# accept this transformation
new_transform.update_module()
transform = new_transform
cost = new_cost
if best_cost > cost:
best_cost = cost
best = copy(transform)
break
t *= (alpha ** skipped)
i += skipped
print 'cost = {0}, best cost = {1}, init cost = {2}, itr = {3}, t = {4}, alpha = {5}'\
.format(cost, best_cost, init_cost, i, t, alpha)
return best.apply()
except KeyboardInterrupt:
pool.terminate()
pool.join()
exit()