Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Queues: benchmark implementations against each other #2276

Merged
merged 22 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
polybeandip marked this conversation as resolved.
Show resolved Hide resolved

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)"
polybeandip marked this conversation as resolved.
Show resolved Hide resolved

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
)
polybeandip marked this conversation as resolved.
Show resolved Hide resolved

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
Loading