Source code for simplebench.iteration

"""Iteration class"""
from .defaults import DEFAULT_INTERVAL_SCALE, DEFAULT_INTERVAL_UNIT
from .doc_utils import format_docstring
from .enums import Section
from .exceptions import SimpleBenchTypeError, SimpleBenchValueError, _IterationErrorTag
from .validators import (
    validate_int,
    validate_non_blank_string,
    validate_non_negative_float,
    validate_positive_float,
    validate_positive_int,
)


[docs] @format_docstring(DEFAULT_INTERVAL_UNIT=DEFAULT_INTERVAL_UNIT, DEFAULT_INTERVAL_SCALE=DEFAULT_INTERVAL_SCALE) class Iteration: """Container for the results of a single benchmark iteration. An iteration represents a single run of a benchmarked action (a run may consist of multiple rounds and a full benchmark consists of multiple iterations). It holds the elapsed time, n weight, unit, scale, memory usage, and peak memory usage for that iteration. Elapsed time is the total time taken for the iteration in the specified unit (e.g., nanoseconds) and scale (e.g., 1e-9 to convert nanoseconds to seconds) divided by the number of rounds as measured in the unit and scale specified. It is the average time per round for the iteration. So if an iteration with a unit of 'ns' and a scale of 1e-9 had an `elapsed` time of 5000000 (5 million nanoseconds) and 20 rounds, then the elapsed time would be equivalent to an average of (5000000 * 1e-9) / 20 = 0.00025 seconds per round. The n-weight represents the O(n) type complexity of the action being benchmarked. For example, if the action processes a list of size n, then the n-weight would be n. This allows data analysis tools to better understand the performance characteristics of the action being benchmarked when the benchmark data is exported, although it is not used directly in any calculations by SimpleBench itself currently. :ivar n: The complexity n-weight for the iteration. (read only) :vartype n: int | float :ivar rounds: The number of rounds in the iteration. (read only) :vartype rounds: int :ivar unit: The unit of measurement for the elapsed time. It gets its default value from :class:`~simplebench.defaults.DEFAULT_INTERVAL_UNIT`. (read only) :vartype unit: str :ivar scale: The scale factor for the elapsed time. It gets its default value from :class:`~simplebench.defaults.DEFAULT_INTERVAL_SCALE`. (read only) :vartype scale: float :ivar elapsed: The elapsed time for the iteration. (read only) :vartype elapsed: float :ivar ops_per_second: The number of operations per second. (read only) :vartype ops_per_second: float :ivar per_round_elapsed: The mean time for a single round scaled to the base unit. (read only) :vartype per_round_elapsed: float :ivar memory: The memory usage in bytes. (defaults to 0) (read only) :vartype memory: int :ivar peak_memory: The peak memory usage in bytes. (read only) :vartype peak_memory: int """ __slots__ = ('_n', '_rounds', '_elapsed', '_unit', '_scale', '_memory', '_peak_memory') @format_docstring(DEFAULT_INTERVAL_UNIT=DEFAULT_INTERVAL_UNIT, DEFAULT_INTERVAL_SCALE=DEFAULT_INTERVAL_SCALE) def __init__(self, *, n: int | float = 1, rounds: int = 1, unit: str = DEFAULT_INTERVAL_UNIT, scale: float = DEFAULT_INTERVAL_SCALE, elapsed: float = 0.0, memory: int = 0, # in bytes peak_memory: int = 0, # in bytes ) -> None: """Initialize an Iteration instance. :param n: The complexity n-weight for the iteration. Must be a positive integer. :type n: int | float :param rounds: The number of rounds in the iteration. Must be a positive integer. :type rounds: int :param unit: The unit of measurement for the elapsed time. It gets its default value from :class:`~simplebench.defaults.DEFAULT_INTERVAL_UNIT`. :type unit: str :param scale: The scale factor for the elapsed time. It gets its default value from :class:`simplebench.defaults.DEFAULT_INTERVAL_SCALE`. :type scale: float :param elapsed: The elapsed time for the iteration. Must be a non-negative float. :type elapsed: float :param memory: The memory usage in bytes. Must be an integer. :type memory: int :param peak_memory: The peak memory usage in bytes. Must be an integer. :type peak_memory: int :raises SimpleBenchTypeError: If any of the arguments are of the wrong type. :raises SimpleBenchValueError: If any of the arguments have invalid values. """ self._n: float = validate_positive_float( n, 'n', _IterationErrorTag.N_ARG_TYPE, _IterationErrorTag.N_ARG_VALUE) self._rounds: int = validate_positive_int( rounds, 'rounds', _IterationErrorTag.ROUNDS_ARG_TYPE, _IterationErrorTag.ROUNDS_ARG_VALUE) self._unit: str = validate_non_blank_string( unit, 'unit', _IterationErrorTag.UNIT_ARG_TYPE, _IterationErrorTag.UNIT_ARG_VALUE) self._scale: float = validate_positive_float( scale, 'scale', _IterationErrorTag.SCALE_ARG_TYPE, _IterationErrorTag.SCALE_ARG_VALUE) self._elapsed: float = validate_non_negative_float( elapsed, 'elapsed', _IterationErrorTag.ELAPSED_ARG_TYPE, _IterationErrorTag.ELAPSED_ARG_VALUE) self._memory: int = validate_int( memory, 'memory', _IterationErrorTag.MEMORY_ARG_TYPE) self._peak_memory: int = validate_int( peak_memory, 'peak_memory', _IterationErrorTag.PEAK_MEMORY_ARG_TYPE) def __eq__(self, other: object) -> bool: """Check equality between two Iteration instances. Two Iteration instances are considered equal if all their attributes are equal. """ if not isinstance(other, Iteration): return False return (self.n == other.n and self.elapsed == other.elapsed and self.unit == other.unit and self.scale == other.scale and self.memory == other.memory and self.peak_memory == other.peak_memory) @property def n(self) -> float: """The 'n' complexity weight of the iteration for O() analysis.""" return self._n @property def rounds(self) -> int: """The number of rounds in the iteration""" return self._rounds @property def unit(self) -> str: """The unit of measurement for the elapsed time.""" return self._unit @property def scale(self) -> float: """The scale factor for the elapsed time.""" return self._scale @property def elapsed(self) -> float: """The total elapsed time for the iteration in the specified unit.""" return self._elapsed @property def memory(self) -> int: """The memory usage in bytes. This is the difference between the allocated memory before and after the action. The edge case of no memory allocated results in a returned value of 0 """ return self._memory @property def peak_memory(self) -> int: """The peak memory usage in bytes. This is the maximum memory allocated during the action. """ return self._peak_memory @property def per_round_elapsed(self) -> float: """The mean time for a single round scaled to the base unit. If elapsed is 0, returns 0.0 The per round computation is the elapsed time divided by the number of rounds in the iteration. The scaling to the base unit is done using the scale factor. This has the effect of converting the elapsed time into the base unit. For example, if the scale factor is 1e-9 then elapsed time in nanoseconds will be converted to seconds. :return: The mean time for a single round scaled to the base unit. :rtype: float """ return self._elapsed * self._scale / self._rounds @property def ops_per_second(self) -> float: """The number of operations per second. This is calculated as the inverse of the elapsed time. The edge cases of 0 elapsed time results in a returned value of 0.0 This would otherwise be an impossible value and so flags a measurement error. """ if self._elapsed == 0.0: return 0.0 return self._rounds / (self._elapsed * self._scale)
[docs] def iteration_section(self, section: Section) -> int | float: """Returns the requested section of the benchmark results. :param section: The section of the results to return. Must be Section.OPS or Section.TIMING. :type section: Section :return: The requested section of the benchmark results. :rtype: Stats """ if not isinstance(section, Section): raise SimpleBenchTypeError( f'Invalid section type: {type(section)}. Must be of type Section.', tag=_IterationErrorTag.ITERATION_SECTION_INVALID_SECTION_ARG_TYPE ) match section: case Section.OPS: return self.ops_per_second case Section.TIMING: return self.per_round_elapsed case Section.MEMORY: return self.memory case Section.PEAK_MEMORY: return self.peak_memory case _: # needed for mypy raise SimpleBenchValueError( f'Invalid section: {section}. Must be Section.OPS or Section.TIMING.', tag=_IterationErrorTag.ITERATION_SECTION_UNSUPPORTED_SECTION_ARG_VALUE )
def __repr__(self) -> str: """Return a string representation of the Iteration instance.""" unit = self.unit.replace("'", "\\'") return (f"Iteration(n={self.n}, elapsed={self.elapsed}, unit='{unit}', " f"scale={self.scale}, rounds={self.rounds}, memory={self.memory}, " f"peak_memory={self.peak_memory})")