1import os 2import random 3from subprocess import Popen, PIPE 4import psutil 5import json 6import sys 7import math 8 9# usage: python3 constantHelper.py JSON_FILE_PATH 10# 11# an example json config file is as follow: 12# visit https://bosc.yuque.com/yny0gi/gr7hyo/oy3dagqi9v97p696 for detail 13# { 14# "constants": [ 15# { 16# "name": "block_cycles_cache_0", 17# "width": 7, 18# "guide": 20, 19# "init": 11 20# }, 21# { 22# "name": "block_cycles_cache_1", 23# "width": 7, 24# "init": 18 25# }, 26# { 27# "name": "block_cycles_cache_2", 28# "width": 7, 29# "init": 127 30# }, 31# { 32# "name": "block_cycles_cache_3", 33# "width": 7, 34# "init": 17 35# } 36# ], 37# "opt_target": [ 38# {"successfully_forward_channel_D": {"policy" :"max", "baseline" :0} }, 39# {"successfully_forward_mshr": {"policy" :"max", "baseline" :0} }, 40# {"dcache.missQueue.entries_0: load_miss_penalty_to_use,": {"policy" :"min", "baseline" :250396} }, 41# {"dcache.missQueue.entries_1: load_miss_penalty_to_use,": {"policy" :"min", "baseline" :5634} }, 42# {"dcache.missQueue.entries_2: load_miss_penalty_to_use,": {"policy" :"min", "baseline" :4599} }, 43# {"dcache.missQueue.entries_3: load_miss_penalty_to_use,": {"policy" :"min", "baseline" :4146} } 44# ], 45 46# "population_num": 50, 47# "iteration_num": 50, 48# "crossover_rate": 50, 49# "mutation_rate": 50, 50 51# "emu_threads": 16, 52# "concurrent_emu": 4, 53# "max_instr": 1000000, 54# "seed": 3888, 55# "work_load": "~/nexus-am/apps/maprobe/build/maprobe-riscv64-xs.bin" 56# } 57 58 59# parameters according to noop 60NOOP_HOME = os.getenv("NOOP_HOME") 61DIFF_PATH = os.path.join(NOOP_HOME, "ready-to-run", "riscv64-nemu-interpreter-so") 62BUILD_PATH = os.path.join(NOOP_HOME, "build") 63EMU_PATH = os.path.join(BUILD_PATH, "emu") 64 65CONFIG_FILE_PREFIX = ".constant_result_" 66PERF_FILE_POSTFIX = "tmp" 67MAXVAL = (1 << 63) - 1 68 69class Constant: 70 def __init__(self, obj: dict) -> None: 71 self.name = obj['name'] 72 self.width = obj['width'] 73 self.guide = (1 << self.width - 1) - 1 if 'guide' not in obj.keys() else obj['guide'] 74 self.init = random.randint(0, self.guide) if 'init' not in obj.keys() else obj['init'] 75 def maxrange(self) -> int: 76 return (1 << self.width) - 1 77 78 79class Config: 80 def __init__(self, constants, opt_target, population_num, iteration_num, crossover_rate, mutation_rate, emu_threads, concurrent_emu, max_instr, seed, work_load) -> None: 81 self.constants = constants 82 self.opt_target = opt_target 83 self.population_num = int(population_num) 84 self.iteration_num = int(iteration_num) 85 self.crossover_rate = int(crossover_rate) 86 self.mutation_rate = int(mutation_rate) 87 self.emu_threads = int(emu_threads) 88 self.concurrent_emu = int(concurrent_emu) 89 self.max_instr = int(max_instr) 90 self.seed = int(seed) 91 self.work_load = work_load 92 def get_ith_constant(self, i) -> Constant: 93 return self.constants[i] 94 def get_constain_num(self) -> int: 95 return len(self.constants) 96 97 98def loadConfig(json_path) -> Config: 99 obj = json.load(open(json_path, "r")) 100 constants = [Constant(obj['constants'][i]) for i in range(len(obj['constants']))] 101 config = Config(constants, obj['opt_target'], obj['population_num'], obj['iteration_num'], obj['crossover_rate'], obj['mutation_rate'], obj['emu_threads'], obj['concurrent_emu'], obj['max_instr'], obj['seed'], obj['work_load']) 102 return config 103 104class RunContext: 105 def __init__(self, config: Config) -> None: 106 self.config = config 107 def checkCoreFree(self) -> None: 108 percent_per_core = psutil.cpu_percent(interval=1 ,percpu=True) 109 acc = 0 110 for i in range(self.config.concurrent_emu * self.config.emu_threads): 111 acc += percent_per_core[i] 112 if acc < (0.1 * (100 * self.config.concurrent_emu * self.config.emu_threads)): 113 return True 114 else: 115 print("no free {} core, core usage:".format(self.config.concurrent_emu * self.config.emu_threads)) 116 print(percent_per_core) 117 return False 118 def getStdIn(self, population: list, id: int) -> str: 119 res = 'echo \"' 120 res += str(len(population[id])) 121 res += '\\n' 122 for item in population[id]: 123 res += item[0] + ' ' + str(item[1]) + '\\n' 124 res += '\"' 125 return res 126 127 def genRunCMD(self, population, id) -> str: 128 coreStart = (id % self.config.concurrent_emu) * self.config.emu_threads 129 coreEnd = ((id % self.config.concurrent_emu) + 1) * self.config.emu_threads - 1 130 stdinStr = self.getStdIn(population, id) 131 return "{} | numactl -m 1 -C {}-{} {} --i {} --diff {} -I {} -s {} 2>{}.{}".format(stdinStr, coreStart, coreEnd, EMU_PATH, self.config.work_load, DIFF_PATH, self.config.max_instr, self.config.seed, os.path.join(BUILD_PATH, CONFIG_FILE_PREFIX + str(id)), PERF_FILE_POSTFIX) 132 133class Solution: 134 def __init__(self, config: Config) -> None: 135 self.config = config 136 self.context = RunContext(config) 137 def genFirstPopulation(self) -> list: 138 res = [] 139 used = [] 140 config = self.config 141 for i in range(config.population_num): 142 candidate = [[config.get_ith_constant(i).name, random.randint(0, config.get_ith_constant(i).maxrange()) % config.get_ith_constant(i).guide] for i in range(config.get_constain_num())] 143 while(candidate in used): 144 candidate = [[config.get_ith_constant(i).name, random.randint(0, config.get_ith_constant(i).maxrange()) % config.get_ith_constant(i).guide] for i in range(config.get_constain_num())] 145 used.append(candidate) 146 res.append(candidate) 147 assert(len(res) == config.population_num) 148 return res 149 def profilling_fitness(self) -> list: 150 fitness = [] 151 lines = [] 152 for idx in range(self.config.population_num): 153 perfFilePath = os.path.join(BUILD_PATH, CONFIG_FILE_PREFIX + str(idx) + '.' + PERF_FILE_POSTFIX) 154 with open(perfFilePath, "r") as fp: 155 lines = fp.readlines() 156 res = 0 157 for line in lines: 158 for opt in config.opt_target: 159 if list(opt.keys())[0] in line: 160 # max and min policy 161 if list(opt.values())[0]['policy'] == 'max': 162 res += int(list(filter(lambda x: x != '', line.split(' ')))[-1]) - int(list(opt.values())[0]['baseline']) 163 elif list(opt.values())[0]['policy'] == 'min': 164 res += int(list(opt.values())[0]['baseline']) - int(list(filter(lambda x: x != '', line.split(' ')))[-1]) 165 fitness.append(res) 166 assert(len(fitness) == self.config.population_num) 167 return fitness 168 def run_one_round(self, population: list) -> None: 169 procs = [] 170 i = 0 171 while i < len(population): 172 if i % self.config.concurrent_emu == 0: 173 for proc in procs: 174 proc.wait() 175 procs.clear() 176 # print(self.context.genRunCMD(population, i)) 177 print(population[i]) 178 procs.append(Popen(args=self.context.genRunCMD(population, i), shell=True, encoding='utf-8', stdin=PIPE, stdout=PIPE, stderr=PIPE)) 179 i += 1 180 for proc in procs: 181 proc.wait() 182 def mutation(self, item: list) -> list: 183 res = [] 184 for val in item: 185 width = 0 186 guide = 0 187 for constant in self.config.constants: 188 if(constant.name == val[0]): 189 width = constant.width 190 guide = constant.guide 191 mask = 1 << random.randint(0, width - 1) 192 if random.randint(0, 100) > self.config.mutation_rate: 193 res.append(val) 194 else: 195 val[1] = (((val[1] & mask) ^ mask) | val[1]) % guide 196 res.append(val) 197 assert(len(item) == len(res)) 198 return res 199 def crossover(self, poplulation: list) -> list: 200 res = [] 201 if len(poplulation) < 2: 202 return poplulation 203 for individual in poplulation: 204 indivi = [] 205 for (index, constant) in enumerate(individual): 206 const = constant 207 if random.randint(0, 100) < self.config.crossover_rate: 208 crossover_target_id = 0 209 while crossover_target_id == index: 210 crossover_target_id = random.randint(0, len(poplulation) - 1) 211 maskMax = 0 212 guide = 0 213 for config_const in self.config.constants: 214 if config_const.name == constant[0]: 215 maskMax = config_const.width 216 guide = config_const.guide 217 maskMax = int(math.log2(guide)) + 1 if (int(math.log2(guide)) + 1 < maskMax) else maskMax 218 maskLen = random.randint(1, maskMax) 219 mask = (1 << maskLen) - 1 220 shiftLen = random.randint(0, maskMax - maskLen) 221 mask = mask << shiftLen 222 const_now = const[1] 223 target_now = poplulation[crossover_target_id][index][1] 224 const_now = ((const_now & ~(mask)) | (target_now & mask)) % guide 225 const = [constant[0], const_now] 226 indivi.append(const) 227 res.append(indivi) 228 assert(len(poplulation) == len(res)) 229 return res 230 def genNextPop(self, curPop, fitness) -> list: 231 nextgen = [] 232 tmp = sorted(zip(curPop, fitness), key=lambda x : x[1], reverse=True) 233 print() 234 print("opt constant in this round is ", list(tmp)[0][0], " fitness is ", int(list(tmp)[0][1])) 235 cross = [] 236 for i in range(len(tmp)): 237 if i < (len(tmp) // 2): 238 # select 239 nextgen.append(tmp[i][0]) 240 else: 241 cross.append(tmp[i][0]) 242 # crossover 243 cross = self.crossover(cross) 244 nextgen = nextgen + cross 245 # mutation 246 for i in range(len(tmp)): 247 nextgen[i] = self.mutation(nextgen[i]) 248 assert(len(curPop) == len(nextgen)) 249 return nextgen 250 251 class HashList: 252 def __init__(self, obj: list) -> None: 253 # obj: [['test1', 38], ['test2', 15]] 254 self.obj = obj 255 def __hash__(self) -> str: 256 res = '' 257 for const in self.obj: 258 res += ' '.join(map(lambda x : str(x), const)) 259 return hash(res) 260 def __eq__(self, __o: object) -> bool: 261 for (idx, const) in enumerate(self.obj): 262 if const != __o.obj[idx]: 263 return False 264 return True 265 266 def gene_cal(self) -> None: 267 globalMap = dict() 268 if(self.config.population_num % 2 != 0): 269 print("gene algrithom must ensure that population_num is an even value") 270 return 271 parentPoplation = self.genFirstPopulation() 272 init_indiv = [] 273 for constant in self.config.constants: 274 const = [] 275 const.append(constant.name) 276 const.append(constant.init) 277 init_indiv.append(const) 278 parentPoplation.pop() 279 parentPoplation.append(init_indiv) 280 for i in range(self.config.iteration_num): 281 if i != 0: 282 print() 283 print("iteration ", i, " begins") 284 print() 285 while True: 286 if self.context.checkCoreFree(): 287 self.run_one_round(parentPoplation) 288 fitness = self.profilling_fitness() 289 for (pop, fit) in zip(parentPoplation, fitness): 290 globalMap[self.HashList(pop)] = fit 291 parentPoplation = self.genNextPop(parentPoplation, fitness) 292 break 293 globalMap = zip(globalMap.keys(), globalMap.values()) 294 globalMap = sorted(globalMap, key=lambda x : x[1], reverse=True) 295 print("opt constant for gene algrithom is ", list(globalMap)[0][0].obj, " fitness", int(list(globalMap)[0][1])) 296 297config = loadConfig(sys.argv[1]) 298Solution(config).gene_cal() 299