Compute MTask affinity in V3VariableOrder (#4991)

Instead of carrying around MTask affinity from scheduling, compute it in
V3VariableOrder (where it is used), by tracing through the code. This
simplifies some code and has the benefit of handling variables
introduced after scheduling. It's worth a few % speed at run-time, and
the new implementation of V3VariableOrder is slightly more efficient,
though the speed/space is still dominated by the TSP sort.
This commit is contained in:
Geza Lore
2024-03-16 16:32:12 +00:00
committed by GitHub
parent e8a9662eb5
commit 292cc54768
6 changed files with 118 additions and 51 deletions
+114 -27
View File
@@ -26,25 +26,85 @@
#include "V3AstUserAllocator.h"
#include "V3EmitCBase.h"
#include "V3ExecGraph.h"
#include "V3TSP.h"
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
using MTaskIdVec = std::vector<bool>; // Used as a bit-set indexed by MTask ID
using MTaskAffinityMap = std::unordered_map<const AstVar*, MTaskIdVec>;
// Trace through code reachable form an MTask and annotate referenced variabels
class GatherMTaskAffinity final : VNVisitorConst {
// NODE STATE
// AstCFunc::user1() // bool: Already traced this function
// AstVar::user1() // bool: Already traced this variable
const VNUser1InUse m_user1InUse;
// STATE
MTaskAffinityMap& m_results; // The result map being built;
const uint32_t m_id; // Id of mtask being analysed
const size_t m_usedIds = ExecMTask::numUsedIds(); // Value of max id + 1
// CONSTRUCTOR
GatherMTaskAffinity(const ExecMTask* mTaskp, MTaskAffinityMap& results)
: m_results{results}
, m_id{mTaskp->id()} {
iterateChildrenConst(mTaskp->bodyp());
}
~GatherMTaskAffinity() = default;
VL_UNMOVABLE(GatherMTaskAffinity);
// VISIT
void visit(AstNodeVarRef* nodep) {
// Cheaper than relying on emplace().second
if (nodep->user1SetOnce()) return;
AstVar* const varp = nodep->varp();
// Ignore TriggerVec. They are big and read-only in the MTask bodies
AstBasicDType* const basicp = varp->dtypep()->basicp();
if (basicp && basicp->isTriggerVec()) return;
// Set affinity bit
MTaskIdVec& affinity = m_results
.emplace(std::piecewise_construct, //
std::forward_as_tuple(varp), //
std::forward_as_tuple(m_usedIds))
.first->second;
affinity[m_id] = true;
}
void visit(AstCFunc* nodep) {
if (nodep->user1SetOnce()) return; // Prevent repeat traversals/recursion
iterateChildrenConst(nodep);
}
void visit(AstNodeCCall* nodep) {
iterateChildrenConst(nodep); // Arguments
iterateConst(nodep->funcp()); // Callee
}
void visit(AstNode* nodep) { iterateChildrenConst(nodep); }
public:
static void apply(const ExecMTask* mTaskp, MTaskAffinityMap& results) {
GatherMTaskAffinity{mTaskp, results};
}
};
//######################################################################
// Establish mtask variable sort order in mtasks mode
class VarTspSorter final : public V3TSP::TspStateBase {
// MEMBERS
const MTaskIdSet& m_mtaskIds; // Mtask we're ordering
static unsigned s_serialNext; // Unique ID to establish serial order
unsigned m_serial; // Serial ordering
const MTaskIdVec& m_mTaskIds; // Mtask we're ordering
static uint32_t s_serialNext; // Unique ID to establish serial order
const uint32_t m_serial = ++s_serialNext; // Serial ordering
public:
// CONSTRUCTORS
explicit VarTspSorter(const MTaskIdSet& mtaskIds)
: m_mtaskIds(mtaskIds) { // Cannot be {} or GCC 4.8 false warning
m_serial = ++s_serialNext; // Cannot be ()/{} or GCC 4.8 false warning
explicit VarTspSorter(const MTaskIdVec& mTaskIds)
: m_mTaskIds{mTaskIds} {
UASSERT(mTaskIds.size() == ExecMTask::numUsedIds(), "Wrong size for MTask ID vector");
}
~VarTspSorter() override = default;
// METHODS
@@ -52,26 +112,20 @@ public:
return operator<(static_cast<const VarTspSorter&>(other));
}
bool operator<(const VarTspSorter& other) const { return m_serial < other.m_serial; }
const MTaskIdSet& mtaskIds() const { return m_mtaskIds; }
const MTaskIdVec& mTaskIds() const { return m_mTaskIds; }
int cost(const TspStateBase* otherp) const override {
return cost(static_cast<const VarTspSorter*>(otherp));
}
int cost(const VarTspSorter* otherp) const {
int cost = diffs(m_mtaskIds, otherp->m_mtaskIds);
cost += diffs(otherp->m_mtaskIds, m_mtaskIds);
// Compute the number of MTasks not shared (Hamming distance)
int cost = 0;
const size_t size = ExecMTask::numUsedIds();
for (size_t i = 0; i < size; ++i) { cost += m_mTaskIds.at(i) ^ otherp->m_mTaskIds.at(i); }
return cost;
}
// Returns the number of elements in set_a that don't appear in set_b
static int diffs(const MTaskIdSet& set_a, const MTaskIdSet& set_b) {
int diffs = 0;
for (int i : set_a) {
if (set_b.find(i) == set_b.end()) ++diffs;
}
return diffs;
}
};
unsigned VarTspSorter::s_serialNext = 0;
uint32_t VarTspSorter::s_serialNext = 0;
class VariableOrder final {
// NODE STATE
@@ -85,6 +139,15 @@ class VariableOrder final {
AstUser1Allocator<AstVar, VarAttributes> m_attributes; // Attributes used for sorting
const MTaskAffinityMap& m_mTaskAffinity;
VariableOrder(AstNodeModule* modp, const MTaskAffinityMap& mTaskAffinity)
: m_mTaskAffinity{mTaskAffinity} {
orderModuleVars(modp);
}
~VariableOrder() = default;
VL_UNCOPYABLE(VariableOrder);
//######################################################################
// Simple sort
@@ -106,14 +169,20 @@ class VariableOrder final {
// Sort by MTask-affinity first, then the same as simpleSortVars
void tspSortVars(std::vector<AstVar*>& varps) {
// Map from "MTask affinity" -> "variable list"
std::map<const MTaskIdSet, std::vector<AstVar*>> m2v;
for (AstVar* const varp : varps) m2v[varp->mtaskIds()].push_back(varp);
std::map<const MTaskIdVec, std::vector<AstVar*>> m2v;
const MTaskIdVec emptyVec(ExecMTask::numUsedIds(), false);
for (AstVar* const varp : varps) {
const auto it = m_mTaskAffinity.find(varp);
const MTaskIdVec& key = it == m_mTaskAffinity.end() ? emptyVec : it->second;
m2v[key].push_back(varp);
}
// Create a TSP sort state for each unique MTaskIdSet, except for the empty set
V3TSP::StateVec states;
for (const auto& pair : m2v) {
if (pair.first.empty()) continue;
states.push_back(new VarTspSorter{pair.first});
const MTaskIdVec& vec = pair.first;
const bool empty = std::find(vec.begin(), vec.end(), true) == vec.end();
if (!empty) states.push_back(new VarTspSorter{vec});
}
// Do the TSP sort
@@ -131,12 +200,12 @@ class VariableOrder final {
// Enumerate by sorted MTaskIdSet, sort within the set separately
for (const V3TSP::TspStateBase* const stateBasep : sortedStates) {
const VarTspSorter* const statep = dynamic_cast<const VarTspSorter*>(stateBasep);
sortAndAppend(m2v[statep->mtaskIds()]);
sortAndAppend(m2v[statep->mTaskIds()]);
VL_DO_DANGLING(delete statep, statep);
}
// Finally add the variables with no known MTask affinity
sortAndAppend(m2v[MTaskIdSet()]);
sortAndAppend(m2v[emptyVec]);
}
void orderModuleVars(AstNodeModule* modp) {
@@ -190,17 +259,35 @@ class VariableOrder final {
}
public:
static void processModule(AstNodeModule* modp) { VariableOrder{}.orderModuleVars(modp); }
static void processModule(AstNodeModule* modp, const MTaskAffinityMap& mTaskAffinity) {
VariableOrder{modp, mTaskAffinity};
}
};
//######################################################################
// V3VariableOrder static functions
void V3VariableOrder::orderAll() {
void V3VariableOrder::orderAll(AstNetlist* netlistp) {
UINFO(2, __FUNCTION__ << ": " << endl);
MTaskAffinityMap mTaskAffinity;
// Gather MTask affinities
if (v3Global.opt.mtasks()) {
netlistp->topModulep()->foreach([&](AstExecGraph* execGraphp) {
for (const V3GraphVertex* vtxp = execGraphp->depGraphp()->verticesBeginp(); vtxp;
vtxp = vtxp->verticesNextp()) {
GatherMTaskAffinity::apply(vtxp->as<const ExecMTask>(), mTaskAffinity);
}
});
}
// Order variables in each module
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp;
modp = VN_AS(modp->nextp(), NodeModule)) {
VariableOrder::processModule(modp);
VariableOrder::processModule(modp, mTaskAffinity);
}
// Done
V3Global::dumpCheckGlobalTree("variableorder", 0, dumpTreeEitherLevel() >= 3);
}