diff options
author | Todd Gamblin <tgamblin@llnl.gov> | 2021-02-11 00:56:56 -0800 |
---|---|---|
committer | Greg Becker <becker33@llnl.gov> | 2021-03-31 14:39:23 -0700 |
commit | 01a6adb5f7d6771150ff01c8ceb413519de0eccf (patch) | |
tree | 03ab5109ba67031be84691ebdcc685efb0db99e9 /LICENSE-MIT | |
parent | fd12cba18b79f1808f317f22672e2b7a8b385536 (diff) | |
download | spack-01a6adb5f7d6771150ff01c8ceb413519de0eccf.tar.gz spack-01a6adb5f7d6771150ff01c8ceb413519de0eccf.tar.bz2 spack-01a6adb5f7d6771150ff01c8ceb413519de0eccf.tar.xz spack-01a6adb5f7d6771150ff01c8ceb413519de0eccf.zip |
specs: use lazy lexicographic comparison instead of key_ordering
We have been using the `@llnl.util.lang.key_ordering` decorator for specs
and most of their components. This leverages the fact that in Python,
tuple comparison is lexicographic. It allows you to implement a
`_cmp_key` method on your class, and have `__eq__`, `__lt__`, etc.
implemented automatically using that key. For example, you might use
tuple keys to implement comparison, e.g.:
```python
class Widget:
# author implements this
def _cmp_key(self):
return (
self.a,
self.b,
(self.c, self.d),
self.e
)
# operators are generated by @key_ordering
def __eq__(self, other):
return self._cmp_key() == other._cmp_key()
def __lt__(self):
return self._cmp_key() < other._cmp_key()
# etc.
```
The issue there for simple comparators is that we have to bulid the
tuples *and* we have to generate all the values in them up front. When
implementing comparisons for large data structures, this can be costly.
This PR replaces `@key_ordering` with a new decorator,
`@lazy_lexicographic_ordering`. Lazy lexicographic comparison maps the
tuple comparison shown above to generator functions. Instead of comparing
based on pre-constructed tuple keys, users of this decorator can compare
using elements from a generator. So, you'd write:
```python
@lazy_lexicographic_ordering
class Widget:
def _cmp_iter(self):
yield a
yield b
def cd_fun():
yield c
yield d
yield cd_fun
yield e
# operators are added by decorator (but are a bit more complex)
There are no tuples that have to be pre-constructed, and the generator
does not have to complete. Instead of tuples, we simply make functions
that lazily yield what would've been in the tuple. If a yielded value is
a `callable`, the comparison functions will call it and recursively
compar it. The comparator just walks the data structure like you'd expect
it to.
The ``@lazy_lexicographic_ordering`` decorator handles the details of
implementing comparison operators, and the ``Widget`` implementor only
has to worry about writing ``_cmp_iter``, and making sure the elements in
it are also comparable.
Using this PR shaves another 1.5 sec off the runtime of `spack buildcache
list`, and it also speeds up Spec comparison by about 30%. The runtime
improvement comes mostly from *not* calling `hash()` `_cmp_iter()`.
Diffstat (limited to 'LICENSE-MIT')
0 files changed, 0 insertions, 0 deletions