From 49151a174af8577c047ef52af22cee6f5655d05b Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Wed, 30 Sep 2026 09:43:35 +0300 Subject: [PATCH 1/2] Speed up device comparisons in netlist compare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EqualDeviceParameters::less no longer builds a std::set per call, Device::parameter_value is inline - new three-way DeviceClass::compare used by Transition::operator< / ==, sharing one delegate lookup with less and equal - identical-pointer early-out in DeviceCompare::equals | benchmark, 1 thread | before (ms) | after (ms) | |---|---:|---:| | netlist_compare_mos4_params/16384 (n=6) | 2759 ± 14 | 1199 ± 11 | | netlist_compare/2048 (n=20) | 22.00 ± 0.19 | 18.03 ± 0.14 | | vexriscv LVS compare phase (n=10) | 12130 ± 70 | 11200 ± 80 | Measured on 2x Intel Xeon Gold 6148 (40 cores), g++ 13 -O2, mean ± standard deviation. Co-Authored-By: GLM 5.3 --- src/db/db/dbDevice.cc | 6 +- src/db/db/dbDevice.h | 13 ++- src/db/db/dbDeviceClass.cc | 53 +++++---- src/db/db/dbDeviceClass.h | 11 ++ src/db/db/dbNetlistCompareGraph.cc | 38 +++++-- src/db/db/dbNetlistCompareUtils.cc | 4 + src/db/unit_tests/dbNetlistCompareTests.cc | 122 +++++++++++++++++++++ 7 files changed, 214 insertions(+), 33 deletions(-) diff --git a/src/db/db/dbDevice.cc b/src/db/db/dbDevice.cc index 4e4b36e16..e1dc4eb58 100644 --- a/src/db/db/dbDevice.cc +++ b/src/db/db/dbDevice.cc @@ -163,11 +163,9 @@ void Device::connect_terminal (size_t terminal_id, Net *net) } } -double Device::parameter_value (size_t param_id) const +double Device::parameter_default_value (size_t param_id) const { - if (m_parameters.size () > param_id) { - return m_parameters [param_id]; - } else if (mp_device_class) { + if (mp_device_class) { const db::DeviceParameterDefinition *pd = mp_device_class->parameter_definition (param_id); if (pd) { return pd->default_value (); diff --git a/src/db/db/dbDevice.h b/src/db/db/dbDevice.h index bf7e4dd32..5ca07e915 100644 --- a/src/db/db/dbDevice.h +++ b/src/db/db/dbDevice.h @@ -282,8 +282,13 @@ class DB_PUBLIC Device /** * @brief Gets the value for the parameter with the given ID + * NOTE: this is a hot path in the netlist compare - the fast path (parameter + * present in the parameter vector) is inline, the fallback is not. */ - double parameter_value (size_t param_id) const; + double parameter_value (size_t param_id) const + { + return m_parameters.size () > param_id ? m_parameters [param_id] : parameter_default_value (param_id); + } /** * @brief Sets the value for the parameter with the given ID @@ -397,6 +402,12 @@ class DB_PUBLIC Device friend class Circuit; friend class Net; + /** + * @brief Gets the default value of the parameter with the given ID + * Returns 0 if there is no such parameter. + */ + double parameter_default_value (size_t param_id) const; + DeviceClass *mp_device_class; DeviceAbstract *mp_device_abstract; std::string m_name; diff --git a/src/db/db/dbDeviceClass.cc b/src/db/db/dbDeviceClass.cc index 8aeec11d0..a7d0a7bb9 100644 --- a/src/db/db/dbDeviceClass.cc +++ b/src/db/db/dbDeviceClass.cc @@ -25,6 +25,8 @@ #include "dbNetlist.h" #include "tlClassRegistry.h" +#include + namespace db { @@ -129,15 +131,9 @@ bool EqualDeviceParameters::less (const db::Device &a, const db::Device &b) cons // compare the remaining parameters with a default precision - std::set seen; - for (std::vector > >::const_iterator c = m_compare_set.begin (); c != m_compare_set.end (); ++c) { - seen.insert (c->first); - } - - const std::vector &pd = primary_device_class (a, b)->parameter_definitions (); - for (std::vector::const_iterator p = pd.begin (); p != pd.end (); ++p) { - if (p->is_primary () && seen.find (p->id ()) == seen.end ()) { - int cmp = compare_parameters (a.parameter_value (p->id ()), b.parameter_value (p->id ())); + for (const auto &p : primary_device_class (a, b)->parameter_definitions ()) { + if (p.is_primary () && ! is_in_compare_set (p.id ())) { + const int cmp = compare_parameters (a.parameter_value (p.id ()), b.parameter_value (p.id ())); if (cmp != 0) { return cmp < 0; } @@ -147,6 +143,14 @@ bool EqualDeviceParameters::less (const db::Device &a, const db::Device &b) cons return false; } +bool EqualDeviceParameters::is_in_compare_set (size_t parameter_id) const +{ + // the compare set is small, so a linear search is cheaper than building a set + return std::any_of (m_compare_set.begin (), m_compare_set.end (), [parameter_id] (const std::pair > &c) { + return c.first == parameter_id; + }); +} + EqualDeviceParameters &EqualDeviceParameters::operator+= (const EqualDeviceParameters &other) { for (std::vector > >::const_iterator c = other.m_compare_set.begin (); c != other.m_compare_set.end (); ++c) { @@ -309,30 +313,37 @@ size_t DeviceClass::terminal_id_for_name (const std::string &name) const // The default compare delegate static EqualDeviceParameters default_compare; -bool DeviceClass::less (const db::Device &a, const db::Device &b) +static const db::DeviceParameterCompareDelegate * +compare_delegate (const db::Device &a, const db::Device &b) { tl_assert (a.device_class () != 0); tl_assert (b.device_class () != 0); const db::DeviceParameterCompareDelegate *pcd = primary_device_class (a, b)->parameter_compare_delegate (); - if (! pcd) { - pcd = &default_compare; - } + return pcd ? pcd : &default_compare; +} - return pcd->less (a, b); +bool DeviceClass::less (const db::Device &a, const db::Device &b) +{ + return compare_delegate (a, b)->less (a, b); } bool DeviceClass::equal (const db::Device &a, const db::Device &b) { - tl_assert (a.device_class () != 0); - tl_assert (b.device_class () != 0); + const auto *pcd = compare_delegate (a, b); + return ! pcd->less (a, b) && ! pcd->less (b, a); +} - const db::DeviceParameterCompareDelegate *pcd = primary_device_class (a, b)->parameter_compare_delegate (); - if (! pcd) { - pcd = &default_compare; +int DeviceClass::compare (const db::Device &a, const db::Device &b) +{ + const auto *pcd = compare_delegate (a, b); + if (pcd->less (a, b)) { + return -1; + } else if (pcd->less (b, a)) { + return 1; + } else { + return 0; } - - return ! pcd->less (a, b) && ! pcd->less (b, a); } // -------------------------------------------------------------------------------- diff --git a/src/db/db/dbDeviceClass.h b/src/db/db/dbDeviceClass.h index 51f26ecc7..e11eb11b0 100644 --- a/src/db/db/dbDeviceClass.h +++ b/src/db/db/dbDeviceClass.h @@ -356,6 +356,8 @@ class DB_PUBLIC EqualDeviceParameters } private: + bool is_in_compare_set (size_t parameter_id) const; + std::vector > > m_compare_set; }; @@ -702,6 +704,15 @@ class DB_PUBLIC DeviceClass */ static bool equal (const db::Device &a, const db::Device &b); + /** + * @brief Compares the parameters of the devices a and b (three-way) + * + * This is the "equal" and "less" operations combined into a single + * call: it returns -1 if a < b, 1 if a > b and 0 if a and b are equal. + * "equal" is the same result as delivered by the "equal" method. + */ + static int compare (const db::Device &a, const db::Device &b); + /** * @brief Registers a compare delegate * diff --git a/src/db/db/dbNetlistCompareGraph.cc b/src/db/db/dbNetlistCompareGraph.cc index 315257e80..496a972be 100644 --- a/src/db/db/dbNetlistCompareGraph.cc +++ b/src/db/db/dbNetlistCompareGraph.cc @@ -113,10 +113,22 @@ Transition::operator< (const Transition &other) const } if (device () != 0) { - DeviceCompare dc; - if (! dc.equals (std::make_pair (device (), cat ()), std::make_pair (other.device (), other.cat ()))) { - return dc (std::make_pair (device (), cat ()), std::make_pair (other.device (), other.cat ())); + + // the category decides first - same priority as before, but without + // calling the device class compare for the actual decision + if (m_cat != other.m_cat) { + return m_cat < other.m_cat; + } + + // a single three-way comparison replaces the "equals" plus "less" calls + // and skips the parameter comparison entirely for identical devices + if (device () != other.device ()) { + int cmp = db::DeviceClass::compare (*device (), *other.device ()); + if (cmp != 0) { + return cmp < 0; + } } + } if (m_id1 != other.m_id1) { @@ -134,6 +146,12 @@ Transition::operator== (const Transition &other) const return false; } + // the ID comparison is a pure conjunction with the other predicates, so + // do the cheap field first + if (m_id1 != other.m_id1) { + return false; + } + if (is_for_subcircuit ()) { if ((subcircuit () != 0) != (other.subcircuit () != 0)) { @@ -147,7 +165,7 @@ Transition::operator== (const Transition &other) const } } - return (m_id1 == other.m_id1); + return true; } else { @@ -156,13 +174,19 @@ Transition::operator== (const Transition &other) const } if (device () != 0) { - DeviceCompare dc; - if (! dc.equals (std::make_pair (device (), cat ()), std::make_pair (other.device (), other.cat ()))) { + + if (m_cat != other.m_cat) { return false; } + + // identical devices are equal under any tolerance scheme + if (device () != other.device () && db::DeviceClass::compare (*device (), *other.device ()) != 0) { + return false; + } + } - return (m_id1 == other.m_id1 && m_id2 == other.m_id2); + return m_id2 == other.m_id2; } } diff --git a/src/db/db/dbNetlistCompareUtils.cc b/src/db/db/dbNetlistCompareUtils.cc index 88663faee..76b751ec1 100644 --- a/src/db/db/dbNetlistCompareUtils.cc +++ b/src/db/db/dbNetlistCompareUtils.cc @@ -197,6 +197,10 @@ DeviceCompare::equals (const std::pair &d1, const st if (d1.second != d2.second) { return false; } + // identical devices are equal under any tolerance scheme + if (d1.first == d2.first) { + return true; + } return db::DeviceClass::equal (*d1.first, *d2.first); } diff --git a/src/db/unit_tests/dbNetlistCompareTests.cc b/src/db/unit_tests/dbNetlistCompareTests.cc index 49a64c93b..e8a8b0c26 100644 --- a/src/db/unit_tests/dbNetlistCompareTests.cc +++ b/src/db/unit_tests/dbNetlistCompareTests.cc @@ -438,6 +438,128 @@ TEST(0_EqualDeviceParameters) EXPECT_EQ (dc.less (d2, d1), false); } +// a compare delegate with a fixed "less" result, independent of the parameters +// (like GenericDeviceParameterCompare, it overrides EqualDeviceParameters::less) +class FixedLessCompare + : public db::EqualDeviceParameters +{ +public: + FixedLessCompare (const db::Device *first, bool less_first_second, bool less_second_first) + : m_first (first), m_less_first_second (less_first_second), m_less_second_first (less_second_first) + { } + + virtual bool less (const db::Device &a, const db::Device &b) const + { + return &a == m_first ? m_less_first_second : m_less_second_first; + } + +private: + const db::Device *m_first; + bool m_less_first_second, m_less_second_first; +}; + +// asserts that compare, less and equal agree on the delegate's "less" answers +static void expect_compare (tl::TestBase *_this, const db::Device &a, const db::Device &b, bool less_ab, bool less_ba) +{ + EXPECT_EQ (db::DeviceClass::compare (a, b), less_ab ? -1 : (less_ba ? 1 : 0)); + EXPECT_EQ (db::DeviceClass::compare (b, a), less_ba ? -1 : (less_ab ? 1 : 0)); + EXPECT_EQ (db::DeviceClass::less (a, b), less_ab); + EXPECT_EQ (db::DeviceClass::less (b, a), less_ba); + EXPECT_EQ (db::DeviceClass::equal (a, b), ! less_ab && ! less_ba); + EXPECT_EQ (db::DeviceClass::equal (b, a), ! less_ab && ! less_ba); +} + +TEST(0_DeviceClassCompare) +{ + db::DeviceClassMOS3Transistor dc; + + db::Device d1 (&dc); + db::Device d2 (&dc); + + // default delegate: primary parameters only, default tolerance + + d1.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 40.0); + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 40.0); + d1.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 20.0); + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 20.0); + + // non-primary parameters are not compared, and neither are parameters beyond the parameter definitions + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 1.0); + d1.set_parameter_value (100, 5.0); + + expect_compare (_this, d1, d2, false, false); + + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 21.0); + expect_compare (_this, d1, d2, true, false); + + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 20.0); + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 41.0); + expect_compare (_this, d1, d2, true, false); + + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 39.0); + expect_compare (_this, d1, d2, false, true); + + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 40.0); + + // explicit delegate with a tolerance and an ignored parameter + + db::EqualDeviceParameters *eqp = new db::EqualDeviceParameters (); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_L, 0.5, 0.0); + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_W, true); // ignore W + dc.set_parameter_compare_delegate (eqp); + + // W is ignored + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_W, 30.0); + expect_compare (_this, d1, d2, false, false); + + // L is inside the absolute tolerance + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 40.4); + expect_compare (_this, d1, d2, false, false); + + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 40.6); + expect_compare (_this, d1, d2, true, false); + + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 39.4); + expect_compare (_this, d1, d2, false, true); + + d2.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_L, 40.0); + + // a non-primary parameter is compared when listed in the compare set + *eqp += db::EqualDeviceParameters (db::DeviceClassMOS3Transistor::param_id_AD, 0.0, 0.0); + + expect_compare (_this, d1, d2, true, false); + + d1.set_parameter_value (db::DeviceClassMOS3Transistor::param_id_AD, 1.0); + expect_compare (_this, d1, d2, false, false); + + // a parameter beyond the parameter definitions can be put into the compare set + // (d1 carries 5.0 there, d2 the default value 0.0) + *eqp += db::EqualDeviceParameters (100, 0.25, 0.0); + + expect_compare (_this, d1, d2, false, true); + + d2.set_parameter_value (100, 5.1); + expect_compare (_this, d1, d2, false, false); + + d2.set_parameter_value (100, 5.4); + expect_compare (_this, d1, d2, true, false); + + // a delegate subclass overriding "less" (like GenericDeviceParameterCompare does) + + dc.set_parameter_compare_delegate (new FixedLessCompare (&d1, false, false)); + expect_compare (_this, d1, d2, false, false); + + dc.set_parameter_compare_delegate (new FixedLessCompare (&d1, true, false)); + expect_compare (_this, d1, d2, true, false); + + dc.set_parameter_compare_delegate (new FixedLessCompare (&d1, false, true)); + expect_compare (_this, d1, d2, false, true); + + // "less" in both directions: compare takes the first one, so equal is false + dc.set_parameter_compare_delegate (new FixedLessCompare (&d1, true, true)); + expect_compare (_this, d1, d2, true, true); +} + TEST(0_NetNameEquivalence) { db::Netlist a, b; From b0637febcaabf85e8e1caf4e3b082ea7697478bb Mon Sep 17 00:00:00 2001 From: Niko Savola Date: Sun, 11 Oct 2026 15:56:05 +0300 Subject: [PATCH 2/2] Address review: three-way compare in DeviceCompare, set stays - the three-way compare moves from a new DeviceClass::compare into the DeviceCompare functor where the less/equal predicates live - Transition::make_key uses the functor compare; same single-call win - EqualDeviceParameters::less keeps the std::set membership test --- src/db/db/dbDeviceClass.cc | 32 ++++++---------------- src/db/db/dbDeviceClass.h | 11 -------- src/db/db/dbNetlistCompareGraph.cc | 28 ++++++------------- src/db/db/dbNetlistCompareUtils.cc | 19 +++++++++++++ src/db/db/dbNetlistCompareUtils.h | 9 ++++++ src/db/unit_tests/dbNetlistCompareTests.cc | 7 +++-- 6 files changed, 51 insertions(+), 55 deletions(-) diff --git a/src/db/db/dbDeviceClass.cc b/src/db/db/dbDeviceClass.cc index a7d0a7bb9..1d67acaab 100644 --- a/src/db/db/dbDeviceClass.cc +++ b/src/db/db/dbDeviceClass.cc @@ -131,9 +131,15 @@ bool EqualDeviceParameters::less (const db::Device &a, const db::Device &b) cons // compare the remaining parameters with a default precision - for (const auto &p : primary_device_class (a, b)->parameter_definitions ()) { - if (p.is_primary () && ! is_in_compare_set (p.id ())) { - const int cmp = compare_parameters (a.parameter_value (p.id ()), b.parameter_value (p.id ())); + std::set seen; + for (std::vector > >::const_iterator c = m_compare_set.begin (); c != m_compare_set.end (); ++c) { + seen.insert (c->first); + } + + const std::vector &pd = primary_device_class (a, b)->parameter_definitions (); + for (std::vector::const_iterator p = pd.begin (); p != pd.end (); ++p) { + if (p->is_primary () && seen.find (p->id ()) == seen.end ()) { + int cmp = compare_parameters (a.parameter_value (p->id ()), b.parameter_value (p->id ())); if (cmp != 0) { return cmp < 0; } @@ -143,14 +149,6 @@ bool EqualDeviceParameters::less (const db::Device &a, const db::Device &b) cons return false; } -bool EqualDeviceParameters::is_in_compare_set (size_t parameter_id) const -{ - // the compare set is small, so a linear search is cheaper than building a set - return std::any_of (m_compare_set.begin (), m_compare_set.end (), [parameter_id] (const std::pair > &c) { - return c.first == parameter_id; - }); -} - EqualDeviceParameters &EqualDeviceParameters::operator+= (const EqualDeviceParameters &other) { for (std::vector > >::const_iterator c = other.m_compare_set.begin (); c != other.m_compare_set.end (); ++c) { @@ -334,18 +332,6 @@ bool DeviceClass::equal (const db::Device &a, const db::Device &b) return ! pcd->less (a, b) && ! pcd->less (b, a); } -int DeviceClass::compare (const db::Device &a, const db::Device &b) -{ - const auto *pcd = compare_delegate (a, b); - if (pcd->less (a, b)) { - return -1; - } else if (pcd->less (b, a)) { - return 1; - } else { - return 0; - } -} - // -------------------------------------------------------------------------------- // DeviceClassTemplateBase class implementation diff --git a/src/db/db/dbDeviceClass.h b/src/db/db/dbDeviceClass.h index e11eb11b0..51f26ecc7 100644 --- a/src/db/db/dbDeviceClass.h +++ b/src/db/db/dbDeviceClass.h @@ -356,8 +356,6 @@ class DB_PUBLIC EqualDeviceParameters } private: - bool is_in_compare_set (size_t parameter_id) const; - std::vector > > m_compare_set; }; @@ -704,15 +702,6 @@ class DB_PUBLIC DeviceClass */ static bool equal (const db::Device &a, const db::Device &b); - /** - * @brief Compares the parameters of the devices a and b (three-way) - * - * This is the "equal" and "less" operations combined into a single - * call: it returns -1 if a < b, 1 if a > b and 0 if a and b are equal. - * "equal" is the same result as delivered by the "equal" method. - */ - static int compare (const db::Device &a, const db::Device &b); - /** * @brief Registers a compare delegate * diff --git a/src/db/db/dbNetlistCompareGraph.cc b/src/db/db/dbNetlistCompareGraph.cc index 496a972be..c9081e77e 100644 --- a/src/db/db/dbNetlistCompareGraph.cc +++ b/src/db/db/dbNetlistCompareGraph.cc @@ -114,19 +114,12 @@ Transition::operator< (const Transition &other) const if (device () != 0) { - // the category decides first - same priority as before, but without - // calling the device class compare for the actual decision - if (m_cat != other.m_cat) { - return m_cat < other.m_cat; - } - - // a single three-way comparison replaces the "equals" plus "less" calls - // and skips the parameter comparison entirely for identical devices - if (device () != other.device ()) { - int cmp = db::DeviceClass::compare (*device (), *other.device ()); - if (cmp != 0) { - return cmp < 0; - } + // a single three-way comparison replaces the "equals" plus "less" + // calls and skips the parameter comparison entirely for identical devices + DeviceCompare dc; + int cmp = dc.compare (std::make_pair (device (), cat ()), std::make_pair (other.device (), other.cat ())); + if (cmp != 0) { + return cmp < 0; } } @@ -175,12 +168,9 @@ Transition::operator== (const Transition &other) const if (device () != 0) { - if (m_cat != other.m_cat) { - return false; - } - - // identical devices are equal under any tolerance scheme - if (device () != other.device () && db::DeviceClass::compare (*device (), *other.device ()) != 0) { + // a single three-way comparison replaces the "equals" call + DeviceCompare dc; + if (dc.compare (std::make_pair (device (), cat ()), std::make_pair (other.device (), other.cat ())) != 0) { return false; } diff --git a/src/db/db/dbNetlistCompareUtils.cc b/src/db/db/dbNetlistCompareUtils.cc index 76b751ec1..e40d0ccc8 100644 --- a/src/db/db/dbNetlistCompareUtils.cc +++ b/src/db/db/dbNetlistCompareUtils.cc @@ -204,6 +204,25 @@ DeviceCompare::equals (const std::pair &d1, const st return db::DeviceClass::equal (*d1.first, *d2.first); } +int +DeviceCompare::compare (const std::pair &d1, const std::pair &d2) const +{ + if (d1.second != d2.second) { + return d1.second < d2.second ? -1 : 1; + } + // identical devices are equal under any tolerance scheme + if (d1.first == d2.first) { + return 0; + } + if (db::DeviceClass::less (*d1.first, *d2.first)) { + return -1; + } else if (db::DeviceClass::less (*d2.first, *d1.first)) { + return 1; + } else { + return 0; + } +} + // -------------------------------------------------------------------------------------------------------------------- // SubCircuitCompare implementation diff --git a/src/db/db/dbNetlistCompareUtils.h b/src/db/db/dbNetlistCompareUtils.h index aa33c9ba2..d4fd7cfe8 100644 --- a/src/db/db/dbNetlistCompareUtils.h +++ b/src/db/db/dbNetlistCompareUtils.h @@ -132,6 +132,15 @@ struct DB_PUBLIC DeviceCompare { bool operator() (const std::pair &d1, const std::pair &d2) const; bool equals (const std::pair &d1, const std::pair &d2) const; + + /** + * @brief Three-way comparison of the devices + * + * This combines "equals" and the "less" operation into a single call: + * it returns -1 if d1 < d2, 1 if d1 > d2 and 0 if both are equal + * under the same criterion "equals" applies. + */ + int compare (const std::pair &d1, const std::pair &d2) const; }; // -------------------------------------------------------------------------------------------------------------------- diff --git a/src/db/unit_tests/dbNetlistCompareTests.cc b/src/db/unit_tests/dbNetlistCompareTests.cc index e8a8b0c26..804b4fa61 100644 --- a/src/db/unit_tests/dbNetlistCompareTests.cc +++ b/src/db/unit_tests/dbNetlistCompareTests.cc @@ -461,8 +461,11 @@ class FixedLessCompare // asserts that compare, less and equal agree on the delegate's "less" answers static void expect_compare (tl::TestBase *_this, const db::Device &a, const db::Device &b, bool less_ab, bool less_ba) { - EXPECT_EQ (db::DeviceClass::compare (a, b), less_ab ? -1 : (less_ba ? 1 : 0)); - EXPECT_EQ (db::DeviceClass::compare (b, a), less_ba ? -1 : (less_ab ? 1 : 0)); + // same category on both sides, so the functor takes the pure device decision + const size_t cat = 0; + db::DeviceCompare dc; + EXPECT_EQ (dc.compare (std::make_pair (&a, cat), std::make_pair (&b, cat)), less_ab ? -1 : (less_ba ? 1 : 0)); + EXPECT_EQ (dc.compare (std::make_pair (&b, cat), std::make_pair (&a, cat)), less_ba ? -1 : (less_ab ? 1 : 0)); EXPECT_EQ (db::DeviceClass::less (a, b), less_ab); EXPECT_EQ (db::DeviceClass::less (b, a), less_ba); EXPECT_EQ (db::DeviceClass::equal (a, b), ! less_ab && ! less_ba);