From fbcd6752e6bdbb430492c04b26bfb9a1d7412c3d Mon Sep 17 00:00:00 2001 From: Robert Ricci Date: Tue, 1 Jul 2003 16:14:42 +0000 Subject: [PATCH] Give assign the ability to make dynamic pclasses. Here's how it works: After building the set of pclasses normally, we make another pass through the vnodes. The goal to create a pclass for each individual node. We disable the node's 'own' pclass to begin with. Then, the _first_ time it gets a vnode mapped to it, we remove it from the 'regular' pclass it belongs to, and enable it's own pclass. Then, if it goes empty, we put it back in its regular pclass and disable it's own. The point of this is to replace -p for use with multiplexed nodes. Instead of disabling pclasses altogheter, which has serious performance implications, we can instead be smart about which pnodes remain equivalent (because nothing's been mapped to them), and which are now different. The result is that on my test topoloy, the time to get a good mapping has gone from over 3 minutes to about 6 seconds. This feature is enabled with the -d option, and the -P option is pretty much mandatory when using it, since it greatly exacerbates the problem of cruft in the ptop file. This satisfies #14 from the todo file: 14. do dynamic pclasses Also bumped up the minimum neighborhood size from 500 to 1000. In some tests I was doing, this resulted in better solutions. --- assign/anneal.cc | 38 ++++++++++----- assign/anneal.h | 10 +--- assign/assign.cc | 10 +++- assign/assign_todo.txt | 9 ---- assign/pclass.cc | 104 +++++++++++++++++++++++++++++++++-------- assign/pclass.h | 16 +++++-- assign/physical.h | 6 ++- assign/score.cc | 29 ++++-------- 8 files changed, 145 insertions(+), 77 deletions(-) diff --git a/assign/anneal.cc b/assign/anneal.cc index 76a55b254..305651f4e 100644 --- a/assign/anneal.cc +++ b/assign/anneal.cc @@ -48,18 +48,6 @@ inline int accept(double change, double temperature) return 0; } -// finds a random pnode, of any type at all -tb_pnode *find_random_pnode() { - int choice = std::random() % num_vertices(PG); - pvertex_iterator vit, vendit; - tie(vit,vendit) = vertices(PG); - cout << "Chose pnode " << choice << " of " << num_vertices(PG) << endl; - for (int i = 0; i < choice;++vit, ++i) { - } - tb_pnode *curP = get(pvertex_pmap,*vit); - return curP; -} - tb_pnode *find_pnode(tb_vnode *vn) { #ifdef PER_VNODE_TT @@ -71,6 +59,19 @@ tb_pnode *find_pnode(tb_vnode *vn) pclass_vector *acceptable_types = tt.second; tb_pnode *newpnode; + + /* + int enabled_pclasses = 0; + for (int i = 0; i < num_types; i++) { + if ((*acceptable_types)[i]->disabled) { + continue; + } + enabled_pclasses++; + } + cout << "Looking for a pnode for " << vn->name << " - there are " << + enabled_pclasses << " to choose from (" << num_types << " total)" << endl; + assert(num_types == enabled_pclasses); + */ int i = std::random()%num_types; int first = i; @@ -97,6 +98,10 @@ REDO_SEARCH: (*acceptable_types)[i]->members.end()) { continue; } + if ((*acceptable_types)[i]->disabled) { + i = std::rand()%num_types; + continue; + } #endif list::iterator it = (*acceptable_types)[i]->members[vn->type]->L.begin(); #ifdef LOAD_BALANCE @@ -898,6 +903,15 @@ NOTQUITEDONE: endl << " This indicates a bug - contact the operators" << endl << " (initial score: " << initial_score << ", current score: " << get_score() << ")" << endl; + // One source of this can be pclasses that are still used - check for + // those + pclass_list::iterator pit = pclasses.begin(); + for (;pit != pclasses.end();pit++) { + if ((*pit)->used_members != 0) { + cerr << (*pit)->name << " is " << (*pit)->used_members + << "% used" << endl; + } + } } tie(vvertex_it,end_vvertex_it) = vertices(VG); for (;vvertex_it!=end_vvertex_it;++vvertex_it) { diff --git a/assign/anneal.h b/assign/anneal.h index 5cbd264a6..517297805 100644 --- a/assign/anneal.h +++ b/assign/anneal.h @@ -30,6 +30,7 @@ using namespace boost; #include "maps.h" #include "score.h" #include "pclass.h" +#include "solution.h" // Some defaults for #defines #ifndef NO_REVERT @@ -50,15 +51,6 @@ using namespace boost; #define PHYSICAL(x) 0 #endif -/* - * Information returned by the annealing process - note that this does not - * include the solution, which is still global for now. - */ -struct annealing_results { - int violated; /* Number of violations - XXX make vinfo non-global too */ - -}; - /* * Globals - XXX made non-global! */ diff --git a/assign/assign.cc b/assign/assign.cc index 93a3d7422..a55d782e9 100644 --- a/assign/assign.cc +++ b/assign/assign.cc @@ -95,6 +95,9 @@ bool disable_pclasses = false; // Whether or not assign should prune out pclasses that it knows can // never be used bool prune_pclasses = false; + +// Whether or not we should use the experimental support for dynamic pclasses +bool dynamic_pclasses = false; // XXX - shouldn't be in this file double absbest; @@ -295,6 +298,7 @@ void print_help() endl; cerr << " -r - Don't allow trivial links." << endl; cerr << " -p - Disable pclasses." << endl; + cerr << " -d - Enable dynamic pclasses." << endl; #ifdef PER_VNODE_TT cerr << " -P - Prune unusable pclasses." << endl; #endif @@ -530,7 +534,7 @@ int main(int argc,char **argv) char ch; timelimit = 0.0; timetarget = 0.0; - while ((ch = getopt(argc,argv,"s:v:l:t:rpP")) != -1) { + while ((ch = getopt(argc,argv,"s:v:l:t:rpPTd")) != -1) { switch (ch) { case 's': if (sscanf(optarg,"%d",&seed) != 1) { @@ -564,6 +568,8 @@ int main(int argc,char **argv) #endif case 'T': scoring_selftest = true; break; + case 'd': + dynamic_pclasses = true; break; default: print_help(); } @@ -608,7 +614,7 @@ int main(int argc,char **argv) calculate_switch_MST(); cout << "Generating physical equivalence classes:"; - generate_pclasses(PG,disable_pclasses); + generate_pclasses(PG,disable_pclasses,dynamic_pclasses); cout << pclasses.size() << endl; #ifdef PCLASS_DEBUG diff --git a/assign/assign_todo.txt b/assign/assign_todo.txt index 9d58c5f89..e6c5ccc9a 100644 --- a/assign/assign_todo.txt +++ b/assign/assign_todo.txt @@ -95,13 +95,4 @@ This is the current TODO list for assign (not in order by priority): up a mapping for an unassignable vnode. Doesn't seem to hurt anything, so is low priority -14. do dynamic pclasses - - pclasses are computed when assign starts. Since a partially-filled - multiplexable pnode is no longer really equivalent to the others in its - pclass, we have to turn off pclasses entirely when using them. If we were to - allow nodes to dynamically leave and enter pclasses, then we wouldn't have - to do this, and runtimes and/or solutions might get better. Probably will be - pretty hard, and/or requires a lot of bookkeeping. - (next free #:15) diff --git a/assign/pclass.cc b/assign/pclass.cc index 4e211b7a7..5eab0a045 100644 --- a/assign/pclass.cc +++ b/assign/pclass.cc @@ -35,6 +35,8 @@ using namespace boost; // fill out the pclass structure. Then two routines pclass_set, and // pclass_unset are used to maintaing the structure during annealing. +// Used to catch cumulative floating point errors +static double ITTY_BITTY = 0.00000001; extern pnode_pvertex_map pnode2vertex; @@ -121,7 +123,8 @@ int pclass_equiv(tb_pgraph &PG, tb_pnode *a,tb_pnode *b) list of all equivalence classes) and type_table (and table of physical type to list of classes that can satisfy that type) are set by this routine. */ -int generate_pclasses(tb_pgraph &PG, bool pclass_for_each_pnode) { +int generate_pclasses(tb_pgraph &PG, bool pclass_for_each_pnode, + bool dynamic_pclasses) { typedef hash_map > pclass_pnode_map; typedef hash_map name_pclass_list_map; @@ -162,6 +165,38 @@ int generate_pclasses(tb_pgraph &PG, bool pclass_for_each_pnode) { } } + if (dynamic_pclasses) { + // Make a pclass for each node, which starts out disabled. It will get + // enabled later when something is assigned to the pnode + pvertex_iterator vit,vendit; + tie(vit,vendit) = vertices(PG); + for (;vit != vendit;++vit) { + tb_pnode *pnode = get(pvertex_pmap,*vit); + // No point in doing this if the pnode is either: already in a pclass of + // size one, or can only have a single vnode mapped to it anyway + if (pnode->my_class->size == 1) { + continue; + } + bool multiplexed = false; + tb_pnode::types_map::iterator it = pnode->types.begin(); + for (; it != pnode->types.end(); it++) { + if ((*it).second->max_load > 1) { + multiplexed = true; + break; + } + } + if (!multiplexed) { + continue; + } + tb_pclass *n = new tb_pclass; + pclasses.push_back(n); + n->name = pnode->name + "-own"; + n->add_member(pnode); + n->disabled = true; + pnode->my_own_class = n; + } + } + name_pclass_list_map pre_type_table; pclass_list::iterator it; @@ -212,6 +247,38 @@ int tb_pclass::add_member(tb_pnode *p) return 0; } +// Debugging function to check the invariant that a node is either in its +// dynamic pclass, a 'normal' pclass, but not both +void assert_own_class_invariant(tb_pnode *p) { + cerr << "class_invariant: " << p->name << endl; + bool own_class = false; + bool other_class = false; + pclass_list::iterator pit = pclasses.begin(); + for (;pit != pclasses.end(); pit++) { + tb_pclass::pclass_members_map::iterator mit = (*pit)->members.begin(); + for (;mit != (*pit)->members.end(); mit++) { + if (mit->second->exists(p)) { + if (*pit == p->my_own_class) { + cerr << "In own pclass (" << (*pit)->name << "," << (*pit)->disabled + << ")" << endl; + if (!(*pit)->disabled) { + own_class = true; + } + } else { + cerr << "In pclass " << (*pit)->name << " type " << mit->first << + " size " << mit->second->size() << endl; + other_class = true; + } + } + } + } + if (own_class && other_class) { + cerr << "Uh oh, in own and other class!" << endl; + pclass_debug(); + abort(); + } +} + // should be called after add_node int pclass_set(tb_vnode *v,tb_pnode *p) { @@ -222,12 +289,13 @@ int pclass_set(tb_vnode *v,tb_pnode *p) for (dit=c->members.begin();dit!=c->members.end();dit++) { if ((*dit).first == p->current_type) { // same class - only remove if node is full - if (p->types[p->current_type]->current_load == - p->types[p->current_type]->max_load) { + if ((p->current_type_record->current_load == + p->current_type_record->max_load) || + p->my_own_class) { (*dit).second->remove(p); -//#ifdef SMART_UNMAP -// c->used_members[(*dit).first]->push_back(p); -//#endif + if (p->my_own_class) { + p->my_own_class->disabled = false; + } } } else { // If it's not in the list then this fails quietly. @@ -246,9 +314,9 @@ int pclass_set(tb_vnode *v,tb_pnode *p) #endif } + c->used_members++; - c->used += 1.0/(p->current_type_record->max_load); - + //assert_own_class_invariant(p); return 0; } @@ -257,35 +325,29 @@ int pclass_unset(tb_pnode *p) // add pnode to all lists in equivalence class. tb_pclass *c = p->my_class; - //cout << "Unassigning " << p->name << ": "; - tb_pclass::pclass_members_map::iterator dit; for (dit=c->members.begin();dit!=c->members.end();++dit) { - //cout << " Type " << dit->first << ": "; if ((*dit).first == p->current_type) { // If it's not in the list then we need to add it to the back if it's // empty and the front if it's not. Since unset is called before // remove_node empty means only one user. if (! (*dit).second->exists(p)) { - assert(p->types[p->current_type]->current_load > 0); + assert(p->current_type_record->current_load > 0); #ifdef PNODE_ALWAYS_FRONT (*dit).second->push_front(p); #else #ifdef PNODE_SWITCH_LOAD - if (p->types[p->current_type]->current_load == 0) { + if (p->current_type_record->current_load == 0) { #else if (p->current_load == 1) { #endif - //cout << "Pushing back: " << p->current_load << " "; (*dit).second->push_back(p); } else { - //cout << "Pushing front: " << p->current_load << " "; (*dit).second->push_front(p); } #endif } } else { - //cout << "Pushing back (2) "; (*dit).second->push_back(p); } @@ -294,10 +356,13 @@ int pclass_unset(tb_pnode *p) #endif } - //cout << endl; + if (p->my_own_class) { + p->my_own_class->disabled = true; + } - c->used -= 1.0/(p->current_type_record->max_load); + c->used_members--; + //assert_own_class_invariant(p); return 0; } @@ -312,7 +377,8 @@ void pclass_debug() cout << "\n"; cout << "Type Table:\n"; pclass_types::iterator dit; - for (dit=type_table.begin();dit!=type_table.end();++dit) { + extern pclass_types vnode_type_table; // from assign.cc + for (dit=vnode_type_table.begin();dit!=vnode_type_table.end();++dit) { cout << (*dit).first << ":"; int n = (*dit).second.first; pclass_vector &A = *((*dit).second.second); diff --git a/assign/pclass.h b/assign/pclass.h index 0b8e979f4..f13c2798d 100644 --- a/assign/pclass.h +++ b/assign/pclass.h @@ -52,6 +52,10 @@ public: return (L.empty() ? NULL : L.front()); }; + int size() { + return L.size(); + }; + friend ostream &operator<<(ostream &o, const tb_pnodelist& l) { pnode_list::const_iterator lit; @@ -64,7 +68,8 @@ public: class tb_pclass { public: - tb_pclass() : name(), size(0), used(0), refcount(0) {;} + tb_pclass() : name(), size(0), used_members(0), refcount(0), disabled(false) + {;} typedef map pclass_members_map; typedef hash_set > tb_pnodeset; @@ -74,12 +79,14 @@ public: crope name; // purely for debugging int size; - double used; + int used_members; pclass_members_map members; #ifdef SMART_UNMAP pclass_members_set used_members; #endif + bool disabled; + // A count of how many nodes can use this pclass // For use with PRUNE_PCLASSES int refcount; @@ -87,7 +94,7 @@ public: friend ostream &operator<<(ostream &o, const tb_pclass& p) { o << p.name << "(" << &p << ") size=" << p.size << - " used=" << p.used << "\n"; + " used_members=" << p.used_members << " disabled=" << p.disabled << "\n"; pclass_members_map::const_iterator dit; for (dit=p.members.begin();dit!=p.members.end();++dit) { o << " " << (*dit).first << ":\n"; @@ -114,7 +121,8 @@ typedef hash_map pclass_types; // Takes two arguments - a physical graph, and a flag indicating whether or not // each physical node should get its own pclass (effectively disabling // pclasses) -int generate_pclasses(tb_pgraph &PG, bool pclass_for_each_pnode); +int generate_pclasses(tb_pgraph &PG, bool pclass_for_each_pnode, + bool dynamic_pclasses); /* The following two routines sets and remove mappings in pclass datastructures */ diff --git a/assign/physical.h b/assign/physical.h index d519267a9..0bcc8641b 100644 --- a/assign/physical.h +++ b/assign/physical.h @@ -75,7 +75,8 @@ public: current_type_record(NULL), total_load(0), switches(), sgraph_switch(), switch_used_links(0), total_interfaces(0), used_interfaces(0), - total_bandwidth(0), my_class(NULL), assigned_nodes(), + total_bandwidth(0), my_class(NULL), + my_own_class(NULL), assigned_nodes(), trivial_bw(0), trivial_bw_used(0) {;} class type_record { @@ -128,6 +129,9 @@ public: tb_pclass *my_class; // the pclass this node belongs to + tb_pclass *my_own_class; // if using DYNAMIC_PCLASSES, a pointer to the + // node's own class + tb_vnode_set assigned_nodes; // the set of vnodes currently assigned int trivial_bw; // the maximum amount of trivial bandwidth diff --git a/assign/score.cc b/assign/score.cc index 06f90538f..572fa03ef 100644 --- a/assign/score.cc +++ b/assign/score.cc @@ -72,6 +72,10 @@ void score_link_endpoints(pedge pe); #define SSUB(amount) score -= amount #endif +// For convenience, so we can easily turn on or off one statement +#define SDEBADD(amount) cerr << "SADD: " << #amount << "=" << amount << " from " << score;score+=amount;cerr << " to " << score << endl +#define SDEBSUB(amount) cerr << "SSUB: " << #amount << "=" << amount << " from " << score;score-=amount;cerr << " to " << score << endl + #ifdef SCORE_DEBUG #define SDEBUG(a) a #else @@ -266,7 +270,8 @@ void remove_node(vvertex vv) #endif // pclass - if ((!disable_pclasses) && pnode->my_class && (pnode->my_class->used == 0)) { + if ((!disable_pclasses) && !(tr->is_static) && pnode->my_class + && (pnode->my_class->used_members == 0)) { SDEBUG(cerr << " freeing pclass" << endl); SSUB(SCORE_PCLASS); } @@ -312,11 +317,7 @@ void remove_node(vvertex vv) // Only unscore the link if the vnode on the other end is assigned - this // way, only the first end to be unmapped causes unscoring if (! dest_vnode->assigned) { - //cerr << "Skipping link unassignment, because " << dest_vnode->name - //<< " is unassigned " << endl; continue; - } else { - //cerr << "Unassigning, " << dest_vnode->name << " is assigned " << endl; } // Find the pnode on the ther end of the link, and unscore it! @@ -507,20 +508,12 @@ int add_node(vvertex vv,pvertex pv, bool deterministic) // Remove check assuming at higher level? // Remove higher level checks? if (!pnode->set_current_type(vnode->type)) { - // pnode->max_load=0; - // if (pnode->types.find(vnode->type) != pnode->types.end()) { - // pnode->max_load = pnode->types[vnode->type]; - // } - //if (pnode->max_load == 0) { // didn't find a type SDEBUG(cerr << " no matching type" << endl); //cerr << "Failed due to bad type!" << endl; return 1; } - // pnode->current_type=vnode->type; - // pnode->typed=true; - SDEBUG(cerr << " matching type found (" << pnode->current_type << ", max = " << pnode->current_type_record->max_load << ")" << endl); } else { @@ -793,14 +786,8 @@ int add_node(vvertex vv,pvertex pv, bool deterministic) if (pnode->total_load == 1) { SDEBUG(cerr << " new pnode" << endl); SADD(SCORE_PNODE); -#ifdef LOAD_BALANCE - //SADD(SCORE_PNODE); // Yep, twice -#endif } #ifdef LOAD_BALANCE - //SSUB(SCORE_PNODE * (1.0 / pnode->max_load)); - //SSUB(SCORE_PNODE * (1 + powf(((pnode->current_load-1) * 1.0)/pnode->max_load,2))); - //SADD(SCORE_PNODE * (1 + powf(((pnode->current_load) * 1.0)/pnode->max_load,2))); SSUB(SCORE_PNODE * (powf(1 + ((pnode->current_load-1) * 1.0)/pnode->max_load,2))); SADD(SCORE_PNODE * (powf(1 + ((pnode->current_load) * 1.0)/pnode->max_load,2))); #endif @@ -818,7 +805,8 @@ int add_node(vvertex vv,pvertex pv, bool deterministic) vinfo.desires += fd_violated; // pclass - if ((!disable_pclasses) && pnode->my_class && (pnode->my_class->used == 0)) { + if ((!disable_pclasses) && (!tr->is_static) && pnode->my_class && + (pnode->my_class->used_members == 0)) { SDEBUG(cerr << " new pclass" << endl); SADD(SCORE_PCLASS); } @@ -1307,7 +1295,6 @@ pvertex make_lan_node(vvertex vv) tb_vnode *vnode = get(vvertex_pmap,vv); SDEBUG(cerr << "make_lan_node(" << vnode->name << ")" << endl); - //cerr << "make_lan_node(" << vnode->name << ")" << endl; // Choose switch pvertex largest_switch; -- GitLab