Skip to content

Commit

Permalink
Queues: benchmark implementations against each other (#2276)
Browse files Browse the repository at this point in the history
Closes #2221 

Changes:
- implement `strict` and `round_robin` scheduling transactions via
`stable_binheap`
- use `generate_name` in `ComponentBuilder.case`; this way, there are
less conflicts when a component uses multiple cases
    - change the associated `case` test
- tweak `gen_test_data.sh` to copy `round_robin` and `strict` data files
into `tests/binheap/`
- make `clean_test_data.sh` purge data files in
`tests/binheap/round_robin` and `tests/binheap/strict`
- make `runt.toml` test our new queues
- setup `cycles.sh` to generate cycles counts for all tests
- setup `resources.sh` to generate synthesis results for all tests
  • Loading branch information
polybeandip authored Sep 25, 2024
1 parent c497dd4 commit f52cc6d
Show file tree
Hide file tree
Showing 24 changed files with 650 additions and 23 deletions.
2 changes: 1 addition & 1 deletion calyx-py/calyx/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def case(
width = self.infer_width(signal)
ifs = []
for branch, controllable in cases.items():
std_eq = self.eq(width, f"{signal.name}_eq_{branch}", signed)
std_eq = self.eq(width, self.generate_name(f"{signal.name}_eq_{branch}"), signed)

with self.continuous:
std_eq.left = signal
Expand Down
16 changes: 8 additions & 8 deletions calyx-py/test/case.expect
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ import "primitives/binary_operators.futil";
component my_comp(in_1: 8) -> (out_1: 16) {
cells {
comp_reg = std_reg(1);
in_1_eq_1 = std_eq(8);
in_1_eq_2 = std_eq(8);
in_1_eq_1_1 = std_eq(8);
in_1_eq_2_2 = std_eq(8);
}
wires {
group my_group {

}
in_1_eq_1.left = in_1;
in_1_eq_1.right = 8'd1;
in_1_eq_2.left = in_1;
in_1_eq_2.right = 8'd2;
in_1_eq_1_1.left = in_1;
in_1_eq_1_1.right = 8'd1;
in_1_eq_2_2.left = in_1;
in_1_eq_2_2.right = 8'd2;
}
control {
par {
if in_1_eq_1.out {
if in_1_eq_1_1.out {
my_group;
}
if in_1_eq_2.out {
if in_1_eq_2_2.out {
invoke comp_reg(in=1'd1)();
}
}
Expand Down
35 changes: 35 additions & 0 deletions frontends/queues/cycles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/bash

shopt -s globstar

cd "$(dirname "$0")/../.." # move to root

declare -a files=(frontends/queues/tests/**/*.py)
num_files=${#files[@]}

echo "{"

for (( i=0; i<${num_files}; i++ )); do
file="${files[$i]}"
name="$(basename $file .py)"
dir="$(dirname $file)"

cycles="$(python3 $file 20000 --keepgoing |\
fud e --from calyx --to jq \
--through verilog \
--through dat \
-s verilog.data "$dir/$name.data" \
-s jq.expr ".cycles" \
-q)"

echo -n "\"${file#*tests/}\" : $cycles"

# because JSON doesn't allow trailing ','s
if [ $i -ne $(( num_files - 1 )) ]; then
echo ","
else
echo ""
fi
done

echo "}"
119 changes: 119 additions & 0 deletions frontends/queues/plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import os
import sys
import json
from enum import Enum
import matplotlib.pyplot as plt


class Logic(Enum):
RR = 1
STRICT = 2

def append_path_prefix(file):
path_to_script = os.path.dirname(__file__)
path_to_file = os.path.join(path_to_script, file)
return path_to_file

def parse(stat, file):
out = {
"binheap" : {
"round_robin" : {},
"strict" : {}
},
"specialized" : {
"round_robin" : {},
"strict" : {}
}
}

with open(file) as file:
data = json.load(file)
for file, data in data.items():
if isinstance(data, dict):
data = data[stat]

flow_no = file.split('flow')[0][-1]

if "round_robin" in file:
if "binheap" in file:
out["binheap"]["round_robin"][flow_no] = data
else:
out["specialized"]["round_robin"][flow_no] = data
if "strict" in file:
if "binheap" in file:
out["binheap"]["strict"][flow_no] = data
else:
out["specialized"]["strict"][flow_no] = data

return out

def draw(data, stat, logic, unit):
fig, ax = plt.subplots(1, 1)
fig.set_size_inches(20, 10, forward=True)
ax.set_xlabel("number of flows", fontsize=20)
if unit is None:
ax.set_ylabel(stat, fontsize=20)
else:
ax.set_ylabel(f"{stat} ({unit})", fontsize=20)

file = ""

if logic == Logic.RR:
specialized = ax.scatter(
data["specialized"]["round_robin"].keys(),
data["specialized"]["round_robin"].values(),
c='b')
binheap = ax.scatter(
data["binheap"]["round_robin"].keys(),
data["binheap"]["round_robin"].values(),
c='g')

ax.set_title("Round Robin Queues", fontweight='bold', fontsize=20)
file = append_path_prefix(f"{stat}_round_robin")

elif logic == Logic.STRICT:
specialized = ax.scatter(
data["specialized"]["strict"].keys(),
data["specialized"]["strict"].values(),
c='b')
binheap = ax.scatter(
data["binheap"]["strict"].keys(),
data["binheap"]["strict"].values(),
c='g')

ax.set_title("Strict Queues", fontweight='bold', fontsize=20)
file = append_path_prefix(f"{stat}_strict")

plt.legend((specialized, binheap),
("Specialized (i.e. Cassandra style)", "Binary Heap"),
fontsize=12)

plt.savefig(file)

print(f"Generated {file}.png")

# Parse data for round_robin and strict queues
stat = sys.argv[1]
data = {}
if stat == "total_time":
file1 = sys.argv[2]
file2 = sys.argv[3]

cycle_data = parse("cycles", file1)
slack_data = parse("worst_slack", file2)

data = cycle_data.copy()
for impl in data.keys():
for logic in data[impl].keys():
for flow_no in data[impl][logic].keys():
cycles = cycle_data[impl][logic][flow_no]
slack = slack_data[impl][logic][flow_no]
data[impl][logic][flow_no] = (1000 * cycles) / (7 - slack)
else:
file = sys.argv[2]
data = parse(stat, file)

# Draw results
unit = "μs" if stat == "total_time" else None
draw(data, stat, Logic.RR, unit)
draw(data, stat, Logic.STRICT, unit)
2 changes: 2 additions & 0 deletions frontends/queues/queues/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
import queues.binheap.stable_binheap as stable_binheap
import queues.binheap.fifo as binheap_fifo
import queues.binheap.pifo as binheap_pifo
import queues.binheap.round_robin as binheap_rr
import queues.binheap.strict as binheap_strict
import queues.binheap.binheap as binheap
33 changes: 33 additions & 0 deletions frontends/queues/queues/binheap/flow_inference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# pylint: disable=import-error
import calyx.builder as cb


def insert_flow_inference(comp, value, flow, boundaries, name):
bound_checks = []

for b in range(len(boundaries)):
lt = comp.lt(32)
le = comp.le(32)
guard = comp.and_(1)

with comp.comb_group(f"{name}_bound_check_{b}") as bound_check_b:
le.left = value
le.right = boundaries[b]
if b > 0:
lt.left = boundaries[b-1]
lt.right = value
else:
lt.left = 0
lt.right = 1
guard.left = le.out
guard.right = lt.out

set_flow_b = comp.reg_store(flow, b, f"{name}_set_flow_{b}")
bound_check = cb.if_with(
cb.CellAndGroup(guard, bound_check_b),
set_flow_b
)

bound_checks.append(bound_check)

return cb.par(*bound_checks)
120 changes: 120 additions & 0 deletions frontends/queues/queues/binheap/round_robin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# pylint: disable=import-error
import calyx.builder as cb
from calyx.utils import bits_needed
from queues.binheap.stable_binheap import insert_stable_binheap
from queues.binheap.flow_inference import insert_flow_inference

FACTOR = 4


def insert_binheap_rr(prog, name, boundaries, queue_size_factor=FACTOR):
n = len(boundaries)

comp = prog.component(name)

binheap = insert_stable_binheap(prog, "binheap", queue_size_factor)
binheap = comp.cell("binheap", binheap)

cmd = comp.input("cmd", 1)
value = comp.input("value", 32)

ans = comp.reg(32, "ans", is_ref=True)
err = comp.reg(1, "err", is_ref=True)

err_eq_0 = comp.eq_use(err.out, 0)

flow_in = comp.reg(bits_needed(n - 1), "flow_in")
infer_flow_in = insert_flow_inference(
comp, value, flow_in, boundaries, "infer_flow_in"
)

flow_out = comp.reg(bits_needed(n - 1), "flow_out")
infer_flow_out = insert_flow_inference(
comp, ans.out, flow_out, boundaries, "infer_flow_out"
)

rank_ptrs = [comp.reg(32, f"r_{i}") for i in range(n)]
rank_ptr_incrs = dict([(i, comp.incr(rank_ptrs[i], n)) for i in range(n)])

turn = comp.reg(bits_needed(n - 1), "turn")
turn_neq_flow_out = comp.neq_use(turn.out, flow_out.out)
turn_incr_mod_n = cb.if_with(
comp.eq_use(turn.out, n - 1),
comp.reg_store(turn, 0),
comp.incr(turn)
)

init = comp.reg(1, "init")
init_eq_0 = comp.eq_use(init.out, 0)
init_state = cb.if_with(
init_eq_0,
[
cb.par(*[ comp.reg_store(rank_ptrs[i], i) for i in range(n) ]),
comp.incr(init)
]
)

def binheap_invoke(value, rank):
return cb.invoke(
binheap,
in_value=value,
in_rank=rank,
in_cmd=cmd,
ref_ans=ans,
ref_err=err,
)
binheap_invokes = dict([
(i, binheap_invoke(value, rank_ptrs[i].out))
for i in range(n)
])

update_state_pop = [
infer_flow_out,
cb.while_with(
turn_neq_flow_out,
[
comp.case(turn.out, rank_ptr_incrs),
turn_incr_mod_n
]
),
turn_incr_mod_n
]
update_state_push = comp.case(flow_in.out, rank_ptr_incrs)

comp.control += [
init_state,
infer_flow_in,
comp.case(flow_in.out, binheap_invokes),
cb.if_with(
err_eq_0,
comp.case(
cmd,
{ 0: update_state_pop, 1: update_state_push }
)
)
]

return comp


def generate(prog, numflows):
"""Generates queue with specific `boundaries`"""

if numflows == 2:
boundaries = [200, 400]
elif numflows == 3:
boundaries = [133, 266, 400]
elif numflows == 4:
boundaries = [100, 200, 300, 400]
elif numflows == 5:
boundaries = [80, 160, 240, 320, 400]
elif numflows == 6:
boundaries = [66, 100, 200, 220, 300, 400]
elif numflows == 7:
boundaries = [50, 100, 150, 200, 250, 300, 400]
else:
raise ValueError("Unsupported number of flows")

pifo = insert_binheap_rr(prog, "pifo", boundaries)

return pifo
Loading

0 comments on commit f52cc6d

Please sign in to comment.