Skip to content

Commit 06ae8c6

Browse files
committed
AnalyserModel: improved and fixed areEquivalentVariables().
Indeed, in our WebAssembly module, the key was 32-bit (while 64-bit in C++/Python) which caused collisions and incorrect cache hits. So, we replaced the Cantor-pairing key and std::map-based cache with a typed VariableKeyPair and std::unordered_map. This makes the cached-equivalence lookup more explicit, portable, and efficient.
1 parent 13bd8aa commit 06ae8c6

2 files changed

Lines changed: 36 additions & 15 deletions

File tree

src/analysermodel.cpp

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -496,25 +496,20 @@ bool AnalyserModel::areEquivalentVariables(const VariablePtr &variable1,
496496
// an AnalyserModel object refers to a static version of a model, which
497497
// means that we can safely cache the result of a call to that utility. In
498498
// turn, this means that we can speed up any feature (e.g., code generation)
499-
// that also relies on that utility. When it comes to the key for the cache,
500-
// we use the Cantor pairing function with the address of the two variables
501-
// as parameters, thus ensuring the uniqueness of the key (see
502-
// https://en.wikipedia.org/wiki/Pairing_function#Cantor_pairing_function).
499+
// that also relies on that utility.
503500

504501
auto v1 = reinterpret_cast<uintptr_t>(variable1.get());
505502
auto v2 = reinterpret_cast<uintptr_t>(variable2.get());
506503

507-
if (v2 < v1) {
508-
v1 += v2;
509-
v2 = v1 - v2;
510-
v1 = v1 - v2;
504+
if (v1 > v2) {
505+
std::swap(v1, v2);
511506
}
512507

513-
auto key = ((v1 + v2) * (v1 + v2 + 1) >> 1U) + v2;
514-
auto cacheKey = mPimpl->mCachedEquivalentVariables.find(key);
508+
auto key = AnalyserModel::AnalyserModelImpl::VariableKeyPair{v1, v2};
509+
auto it = mPimpl->mCachedEquivalentVariables.find(key);
515510

516-
if (cacheKey != mPimpl->mCachedEquivalentVariables.end()) {
517-
return cacheKey->second;
511+
if (it != mPimpl->mCachedEquivalentVariables.end()) {
512+
return it->second;
518513
}
519514

520515
auto res = libcellml::areEquivalentVariables(variable1, variable2);

src/analysermodel_p.h

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ limitations under the License.
1616

1717
#pragma once
1818

19-
#include <map>
19+
#include <cstdint>
20+
#include <unordered_map>
2021

2122
#include "libcellml/analysermodel.h"
2223

@@ -45,6 +46,33 @@ struct AnalyserModel::AnalyserModelImpl
4546

4647
std::vector<AnalyserEquationPtr> mAnalyserEquations;
4748

49+
struct VariableKeyPair
50+
{
51+
uintptr_t first;
52+
uintptr_t second;
53+
54+
bool operator==(const VariableKeyPair& other) const
55+
{
56+
return first == other.first && second == other.second;
57+
}
58+
};
59+
60+
struct VariableKeyPairHash
61+
{
62+
size_t operator()(const VariableKeyPair& pair) const
63+
{
64+
// A simple and portable hash function for a pair of pointers.
65+
66+
size_t hash = pair.first;
67+
68+
hash ^= pair.second + 0x9e3779b9 + (hash << 6) + (hash >> 2);
69+
70+
return hash;
71+
}
72+
};
73+
74+
std::unordered_map<VariableKeyPair, bool, VariableKeyPairHash> mCachedEquivalentVariables;
75+
4876
bool mNeedEqFunction = false;
4977
bool mNeedNeqFunction = false;
5078
bool mNeedLtFunction = false;
@@ -72,8 +100,6 @@ struct AnalyserModel::AnalyserModelImpl
72100
bool mNeedAcschFunction = false;
73101
bool mNeedAcothFunction = false;
74102

75-
std::map<uintptr_t, bool> mCachedEquivalentVariables;
76-
77103
static AnalyserModelPtr create(const ModelPtr &model = nullptr);
78104

79105
AnalyserModelImpl(const ModelPtr &model);

0 commit comments

Comments
 (0)