Skip to content

Commit

Permalink
Print solver's bound for # of cycles in generated assembly
Browse files Browse the repository at this point in the history
The solver provides us with a lower/upper bound for the objective
it is minimizing/maximizing.

In the case where we minimize the number of cycles, print the
objective bound in the emitted assembly, so it's clear if the
code is optimal according to the model, or can perhaps be improved
by running SLOTHY with larger timeout.
  • Loading branch information
hanno-becker committed Mar 31, 2024
1 parent 690a30c commit 0fa0182
Showing 1 changed file with 58 additions and 16 deletions.
74 changes: 58 additions & 16 deletions slothy/core/core.py
Original file line number Diff line number Diff line change
@@ -234,13 +234,32 @@ def cycles(self):
count per iteration."""
return (self.codesize_with_bubbles // self.config.target.issue_rate)

@property
def cycles_bound(self):
"""A lower bound for the number of cycles obtained during optimization.
This may be lower than the estimated cycle count of the result itself if optimization
terminated prematurely, e.g. because of a timeout."""
return self._cycles_bound

@property
def ipc_bound(self):
"""An uppwer bound on the instruction/cycle (IPC) count obtained during optimization.
This may be lower than the IPC value of the result itself if optimization
terminated prematurely, e.g. because of a timeout."""
cc = self.cycles_bound
if cc is None or cc == 0:
return None
return (self.codesize / cc)

@property
def ipc(self):
"""The instruction/cycle (IPC) count that SLOTHY thinks the code will have."""
cc = self.cycles
if cc == 0:
return 0
return (self.codesize / self.cycles)
return (self.codesize / cc)

@property
def orig_code_visualized(self):
@@ -623,6 +642,13 @@ def gen_visualized_code():
res.append(SourceLine("") \
.set_comment(f"Expected IPC: {self.ipc:.2f}") \
.set_length(fixlen))
if self.cycles_bound is not None:
res.append(SourceLine("") \
.set_comment(f"Cycle bound: {self.cycles_bound}") \
.set_length(fixlen))
res.append(SourceLine("") \
.set_comment(f"IPC bound: {self.ipc_bound:.2f}") \
.set_length(fixlen))

res += list(gen_visualized_code())
res += self.orig_code_visualized
@@ -844,6 +870,11 @@ def stalls(self, v):
assert self._stalls is None
self._stalls = v

@cycles_bound.setter
def cycles_bound(self, v):
assert self._cycles_bound is None
self._cycles_bound = v

def _build_stalls_idxs(self):
self._stalls_idxs = { j for (i,j) in self.reordering.items() if
self.reordering_with_bubbles[i] + 1 not in
@@ -1155,6 +1186,7 @@ def __init__(self, config):
self._reordering_with_bubbles = None
self._valid = False
self._success = None
self._cycles_bound = None
self._stalls = None
self._stalls_idxs = None
self._input = None
@@ -1573,14 +1605,14 @@ def on_solution_callback(self):
bound = self.BestObjectiveBound()
time = self.WallTime()
if self.__printer is not None:
add_cur = self.__printer(cur)
add_bound = self.__printer(bound)
cur_str = self.__printer(cur)
bound_str = self.__printer(bound)
else:
add_cur = ""
add_bound = ""
cur_str = str(cur)
bound_str = str(bound)
self.__logger.info(
f"[{time:.4f}s]: Found {self.__solution_count} solutions so far... " +
f"objective {cur}{add_cur}, bound {bound}{add_bound} ({self.__objective_desc})")
f"objective ({self.__objective_desc}): currently {cur_str}, bound {bound_str}")
if self.__is_good_enough and self.__is_good_enough(cur, bound):
self.StopSearch()
if self.__solution_count >= self.__max_solutions:
@@ -1721,6 +1753,9 @@ def _extract_positions(self, get_value):

if self.config.variable_size:
self._result.stalls = get_value(self._model.stalls)
stalls_bound = self._model.cp_solver.BestObjectiveBound()
cycles_bound, _ = self._stalls_to_stats(stalls_bound)
self._result.cycles_bound = cycles_bound

nodes = self._model.tree.nodes
if self.config.sw_pipelining.enabled:
@@ -2959,6 +2994,21 @@ def restrict_slots_for_instructions_by_property(self, filter_func, slots):
# OBJECTIVES #
# ==============================================================#

def _stalls_to_stats(self, stalls):
psize = self._model.min_slots + \
self._model.pfactor * stalls
cc = psize // self.config.target.issue_rate
cs = self._model.tree.num_nodes
if cc == 0:
return None
cycles = psize // self._model.pfactor
ipc = cs / cc
return (cycles, ipc)

def _print_stalls(self, stalls):
(cycles, ipc) = self._stalls_to_stats(stalls)
return f" (Cycles ~ {cycles}, IPC ~ {ipc:.2f})"

def _add_objective(self, force_objective=False):
minlist = []
maxlist = []
@@ -2969,17 +3019,9 @@ def _add_objective(self, force_objective=False):

# If the number of stalls is variable, its minimization is our objective
if force_objective is False and self.config.variable_size:
name = "minimize number of stalls"
name = "minimize cycles"
if self.config.constraints.functional_only is False:
def get_cpi(stalls):
psize = self._model.min_slots + \
self._model.pfactor * stalls
cc = psize // self.config.target.issue_rate
cs = self._model.tree.num_nodes
if cc == 0:
return ""
return f" (Cycles ~ {psize // self._model.pfactor}, IPC ~ {cs / cc:.2f})"
printer = get_cpi
printer = self._print_stalls
minlist = [self._model.stalls]
elif self.config.has_objective and not self.config.ignore_objective:
if self.config.sw_pipelining.enabled is True and \

0 comments on commit 0fa0182

Please sign in to comment.