Skip to content
This repository has been archived by the owner on Dec 10, 2024. It is now read-only.

Commit

Permalink
Picker: Perf test; Cache e-series (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
iopapamanoglou authored Sep 6, 2024
1 parent d82e61e commit 1437458
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 31 deletions.
12 changes: 11 additions & 1 deletion src/faebryk/libs/e_series.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import logging
from math import ceil, floor, log10
from typing import Tuple
Expand Down Expand Up @@ -429,13 +430,20 @@ def repeat_set_over_base(
class ParamNotResolvedError(Exception): ...


_e_series_cache: list[tuple[Parameter, int, set]] = []


def e_series_intersect[T: float | Quantity](
value: Parameter[T], e_series: E_SERIES = E_SERIES_VALUES.E_ALL
) -> F.Set[T]:
# TODO this got really uglu, need to clean up

value = value.get_most_narrow()

for k, i, v in _e_series_cache:
if k == value and i == id(e_series):
return F.Set(v)

if isinstance(value, F.Constant):
value = F.Range(value)
elif isinstance(value, F.Set):
Expand Down Expand Up @@ -476,7 +484,9 @@ def e_series_intersect[T: float | Quantity](
e_series_values = repeat_set_over_base(
e_series, 10, range(floor(log10(min_val)), ceil(log10(max_val)) + 1)
)
return value & {e * unit for e in e_series_values}
out = value & {e * unit for e in e_series_values}
_e_series_cache.append((copy.copy(value), id(e_series), out.params))
return out


def e_series_discretize_to_nearest(
Expand Down
4 changes: 2 additions & 2 deletions src/faebryk/libs/picker/jlcpcb/jlcpcb.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,8 @@ def is_db_up_to_date(
)

def prompt_db_update(self, prompt: str = "Update JLCPCB database?") -> bool:
ans = input(prompt + " [Y/n]:").lower()
return ans == "y" or ans == ""
ans = input(prompt + " [y/N]:").lower()
return ans == "y"

def download(
self,
Expand Down
47 changes: 47 additions & 0 deletions src/faebryk/libs/test/times.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# This file is part of the faebryk project
# SPDX-License-Identifier: MIT

import time
from textwrap import indent


class Times:
def __init__(self) -> None:
self.times = {}
self.last_time = time.time()

def add(self, name: str):
now = time.time()
if name not in self.times:
self.times[name] = now - self.last_time
self.last_time = now

def _format_val(self, val: float):
return f"{val * 1000:.2f}ms"

def __repr__(self):
formatted = {
k: self._format_val(v)
for k, v in self.times.items()
if not k.startswith("_")
}
longest_name = max(len(k) for k in formatted)
return "Timings: \n" + indent(
"\n".join(f"{k:>{longest_name}}: {v:<10}" for k, v in formatted.items()),
" " * 4,
)

class Context:
def __init__(self, name: str, times: "Times"):
self.name = name
self.times = times

def __enter__(self):
self.times.add("_" + self.name)
self.start = time.time()

def __exit__(self, exc_type, exc_value, traceback):
self.times.times[self.name] = time.time() - self.start

def context(self, name: str):
return Times.Context(name, self)
29 changes: 1 addition & 28 deletions test/core/test_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import time
import unittest
from itertools import pairwise
from textwrap import indent
from typing import Callable

import faebryk.library._F as F
Expand All @@ -13,36 +12,10 @@
from faebryk.core.moduleinterface import ModuleInterface
from faebryk.core.node import Node
from faebryk.libs.library import L
from faebryk.libs.test.times import Times
from faebryk.libs.util import times


class Times:
def __init__(self) -> None:
self.times = {}
self.last_time = time.time()

def add(self, name: str):
now = time.time()
if name not in self.times:
self.times[name] = now - self.last_time
self.last_time = now

def _format_val(self, val: float):
return f"{val * 1000:.2f}ms"

def __repr__(self):
formatted = {
k: self._format_val(v)
for k, v in self.times.items()
if not k.startswith("_")
}
longest_name = max(len(k) for k in formatted)
return "Timings: \n" + indent(
"\n".join(f"{k:>{longest_name}}: {v:<10}" for k, v in formatted.items()),
" " * 4,
)


class TestPerformance(unittest.TestCase):
def test_get_all(self):
def _factory_simple_resistors(count: int):
Expand Down
81 changes: 81 additions & 0 deletions test/libs/picker/test_jlcpcb.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from faebryk.libs.picker.jlcpcb.jlcpcb import JLCPCB_DB
from faebryk.libs.picker.jlcpcb.pickers import add_jlcpcb_pickers
from faebryk.libs.picker.picker import DescriptiveProperties, has_part_picked
from faebryk.libs.test.times import Times
from faebryk.libs.units import P, Quantity

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -360,6 +361,86 @@ def tearDown(self):
JLCPCB_DB.get().close()


@unittest.skipIf(not JLCPCB_DB.config.db_path.exists(), reason="Requires large db")
class TestPickerPerformanceJLCPCB(unittest.TestCase):
def test_simple_full(self):
# conclusions
# - first pick overall is slow, need to load sqlite into buffer cache
# - first pick of component type is slower than subsequent picks
# (even with different parameters)
# - component type order has no influence
# - component type speed differs a lot (res = 500ms, cap = 100ms)
# (even though both value based)
# e-series speed (query or count), if resistor with E24, 200ms
# still 2x though, maybe total count?
# - e-series intersect 20% execution time
# => optimized with cache

timings = Times()

def r_builder(resistance_kohm: float):
return F.Resistor().builder(
lambda r: (
r.resistance.merge(
F.Range.from_center_rel(resistance_kohm * P.kohm, 0.1)
),
r.rated_power.merge(F.ANY()),
r.rated_voltage.merge(F.ANY()),
)
)

def c_builder(capacitance_pf: float):
return F.Capacitor().builder(
lambda c: (
c.capacitance.merge(
F.Range.from_center_rel(capacitance_pf * P.pF, 0.1)
),
c.rated_voltage.merge(F.ANY()),
c.temperature_coefficient.merge(F.ANY()),
)
)

resistors = [r_builder(5 * (i + 1)) for i in range(5)] + [
r_builder(5 * (i + 1)) for i in reversed(range(5))
]
caps = [c_builder(10 * (i + 1)) for i in range(5)] + [
c_builder(10 * (i + 1)) for i in reversed(range(5))
]
resistors_10k = [r_builder(10) for _ in range(10)]

mods = resistors + caps + resistors_10k

for mod in mods:
add_jlcpcb_pickers(mod)

with timings.context("resistors"):
for i, r in enumerate(resistors):
r.get_trait(F.has_picker).pick()
timings.add(
f"full pick value pick (resistor {i}:"
f" {r.resistance.as_unit_with_tolerance('ohm')})"
)

# cache is warm now, but also for non resistors?
with timings.context("capacitors"):
for i, c in enumerate(caps):
c.get_trait(F.has_picker).pick()
timings.add(
f"full pick value pick (capacitor {i}:"
f" {c.capacitance.as_unit_with_tolerance('F')})"
)

with timings.context("resistors_10k"):
for i, r in enumerate(resistors_10k):
r.get_trait(F.has_picker).pick()
timings.add(
f"full pick value pick (resistor {i}:"
f" {r.resistance.as_unit_with_tolerance('ohm')})"
)

print(timings)


if __name__ == "__main__":
setup_basic_logging()
logger.setLevel(logging.DEBUG)
Expand Down

0 comments on commit 1437458

Please sign in to comment.