Commit 1eb1e1d9 authored by Christopher Alfeld's avatar Christopher Alfeld

This check-in consists of 7 modifications to assign.

1. Equivalence Classes

Defined an equivalence relation on the physical nodes and applied it
to the physical topology to get the resulting quotient topology (abuse
of terminology).  So instead of searching among all possible physical
nodes to make a map, assign only searches among all possible
equivalence classes of nodes.  This tremendously reduces the search
space.  At the time of this writing it reduces the physical topology
from 252 nodes to 13 nodes.  The equivalence classes are generated
automatically from the ptop file.

2. Scoring based on equivalence classes.

Each equivalence class used comes with a significant cost.  This
strongly encourages assign to use equivalence machines when possible.
The result is that an experiment that does not otherwise specify will
almost definitely get machines of the same type.  If this needs to be
reduced in the future it is the SCORE_PCLASS constant.

3. Heuristics

Added a bunch of heuristics for choosing which equivalence class to
use.  This was less successful than I hoped.  A good solution is now
found in record time but it still continues searching.  When OPTIMAL
is turned on these heuristics help a lot.  When off they make little
difference.  I may turn this into a compile time option in the future
since the heuristics do take non-trivial CPU cycles.

4. Fixed the very-very-big-and-evil disconnected-switches bug.

Assign wasn't cleaning up after itself in certain cases.  Disconnected
graphs are now merely a minor, easily ignored, bump rather than the
towering cliffs they use to be.

5. Fixed the not-yet-noticed not-enough-nodes bug.

Found a bug that probably has never come up before because we have
checks that avoid those circumstances.

6. Modified constants.

I was tired of waiting so long for results so, I lowered CYCLES and
reduced the constant for naccepts (Mac, you probably want to add that
inconspicuous number to your configurable constants; look for
"naccepts =").  The results is roughly a speedup of 2.  It works great
currently but we may want to change these numbers up again if we get
problems with features and desires.

7. General clean up.

Associated with the other changes was a lot of restructuring and some
cleanup.  Specifically to the assign loop and scoring code.
parent e30b66aa
......@@ -14,7 +14,7 @@ include $(TESTBED_SRCDIR)/GNUmakerules
LEDA=@LEDA@
OBJS=score.o parse_top.o parse_ptop.o config.o
OBJS=score.o parse_top.o parse_ptop.o config.o pclass.o
LIBS+=-L${LEDA} -lD3 -lW -lP -lG -lL -L/usr/X11R6/lib -lX11 -lm -L.
LDFLAGS+= -O3
CXXFLAGS = -I${LEDA}/incl
......@@ -22,8 +22,9 @@ CXXFLAGS = -I${LEDA}/incl
# Pick one of the following:
CXXFLAGS += -Wall -O3
#CXXFLAGS += -O0 -g -Wall -DVERBOSE
#CXXFLAGS += -O0 -g -Wall -DVERBOSE -DSCORE_DEBUG
#CXXFLAGS += -O0 -g -Wall -DVERBOSE -DSCORE_DEBUG -DSCORE_DEBUG_MORE
#CXXFLAGS += -O0 -g -Wall -DVERBOSE -DSCORE_DEBUG -DPCLASS_DEBUG
#CXXFLAGS += -O0 -g -Wall -DVERBOSE -DSCORE_DEBUG -DPCLASS_DEBUG -DPCLASS_DEBUG_MORE
#CXXFLAGS += -O0 -g -Wall -DVERBOSE -DSCORE_DEBUG -DSCORE_DEBUG_MORE -DPCLASS_DEBUG -DPCLASS_DEBUG_MORE
#CXXFLAGS += -DSTATS
......
......@@ -91,3 +91,9 @@ compile time options
what is going on.
-DSCORE_DEBUG_MORE adds even more output.
-DSCORE_PCLASS spits out the equivalence classes at the beginning.
-DSCORE_PCLASS_MORE spits out lots of output during the assign loop.
It works best with -DSCORE_DEBUG but does nto require
-DSCORE_DEBUG_MORE.
......@@ -19,6 +19,7 @@
#include "physical.h"
#include "virtual.h"
#include "score.h"
#include "pclass.h"
void parse_options(char **argv, struct config_param options[], int nopt);
int config_parse(char **args, struct config_param cparams[], int nparams);
......@@ -40,10 +41,15 @@ tb_sgraph SG;
edge_array<int> edge_costs;
typedef node_array<int> switch_distance_array;
typedef node_array<edge> switch_pred_array;
node_array<switch_distance_array> switch_distances;
node_array<switch_pred_array> switch_preds;
tb_pgraph PG;
tb_vgraph G;
dictionary<tb_pnode*,node> pnode2node;
dictionary<tb_pnode*,int> pnode2posistion;
pclass_list pclasses;
dictionary<string,pclass_list*> type_table;
/* How can we chop things up? */
#define PARTITION_BY_ANNEALING 0
......@@ -83,6 +89,14 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i);
list<string> vtypes;
list<string> ptypes;
// Makes LEDA happy
int compare(tb_pnode *const &a, tb_pnode *const &b)
{
if (a==b) return 0;
if (a < b) return -1;
return 1;
}
/*
* Basic simulated annealing parameters:
......@@ -110,6 +124,37 @@ inline int accept(float change, float temperature)
return 0;
}
// This routine chooses randomly chooses a pclass based on the weights
// and *removes that pclass from the weights*. Total is the total of
// all weights and is adjusted when a pclass is removed.
tb_pnode *choose_pnode(dictionary<tb_pclass*,double> &weights,double &total,
string vtype)
{
tb_pnode *pnode;
dic_item dit=nil;
double r = random()/(double)RAND_MAX*total;
forall_items(dit,weights) {
r -= weights.inf(dit);
if (r <= 0) break;
}
if (dit == nil) return NULL;
tb_pclass *chosen_class = weights.key(dit);
// Take the first node of the correct type from the class.
pnode = chosen_class->members.access(vtype)->front();
total -= weights.inf(dit);
weights.del_item(dit);
#ifdef PCLASS_DEBUG_MORE
cout << "choose_pnode = [" << chosen_class->name << "] = "
<< ((pnode == NULL) ? string("NULL"):pnode->name) << endl;
#endif
return pnode;
}
/*
* The workhorse of our program.
*
......@@ -145,7 +190,7 @@ int assign()
int mintrans = (int)cycles;
int trans;
int naccepts = 40*nnodes;
int naccepts = 20*nnodes;
int accepts = 0;
int oldpos;
......@@ -191,7 +236,6 @@ int assign()
trans = 0;
accepts = 0;
bool unassigned = true;
while (trans < mintrans && accepts < naccepts) {
#ifdef STATS
cout << "STATS temp:" << temp << " score:" << get_score() <<
......@@ -209,36 +253,135 @@ int assign()
// Note: we have a lot of +1's here because of the first
// node loc in pnodes is 1 not 0.
oldpos = G[n].posistion;
newpos = ((oldpos+(random()%(nparts-1)+1))%nparts)+1;
if (oldpos != 0) {
remove_node(n);
unassigned = false;
unassigned_nodes.insert(n,random());
}
//////////////////////////////////////////////////////////////////////
// Lots of code to calculate the relative weights of the pclasses.
// The weights are stored in the weights dictionary which is indexed
// by pclass* and contains the weight.
//////////////////////////////////////////////////////////////////////
dictionary<tb_pclass*,double> weights;
list_item lit;
dic_item dit;
// find acceptable pclasses
tb_vnode &vn=G[n];
pclass_list *L = type_table.access(vn.type);
forall_items(lit,*L) {
tb_pclass *c = L->inf(lit);
if (c->used != c->size) {
if (c->members.access(vn.type)->front() != NULL) {
weights.insert(c,PCLASS_BASE_WEIGHT);
}
}
}
// adjust weight by neighbors
edge e;
forall_inout_edges(e,n) {
node dst = G.target(e);
if (dst == n) dst = G.source(e);
tb_vnode &vdst = G[dst];
if (vdst.posistion != 0) {
tb_pnode &pdst = PG[pnodes[vdst.posistion]];
tb_pclass *c = pdst.my_class;
dit = weights.lookup(c);
if (dit != nil) {
weights.change_inf(dit,weights.inf(dit)+PCLASS_NEIGHBOR_WEIGHT);
}
}
}
// Adjust classes by utilizaiton
forall_items(lit,pclasses) {
tb_pclass *c = pclasses.inf(lit);
dit = weights.lookup(c);
if (dit != nil) {
if (c->used > 0) {
weights.change_inf(dit,weights.inf(dit)+
PCLASS_UTIL_WEIGHT);
}
}
}
int first_newpos = newpos;
while (add_node(n,newpos) == 1) {
newpos = (newpos % nparts)+1;
if (newpos == first_newpos) {
// no place to put a node.
// instead re randomly choose a node and remove it and
// then continue the 'continue'
// XXX - could improve this
node ntor = G.choose_node();
while (G[ntor].posistion == 0)
// Adjust classes by features/desires
// We calculate the fd score for all possible classes and normalize
// to 1 and then multiply by PCLASS_FD_WEIGHT
dictionary<tb_pclass*,double> fdscores;
double max_afds=0;
forall_items(dit,weights) {
tb_pclass *c = weights.key(dit);
tb_pnode *p = c->members.access(vn.type)->front();
int v;
double score = fd_score(vn,*p,&v);
fdscores.insert(c,score);
if (fabs(score) > max_afds) max_afds=fabs(score);
}
if (max_afds == 0) max_afds=1;
forall_items(dit,weights) {
tb_pclass *c = weights.key(dit);
double fds = fdscores.access(c);
weights.change_inf(dit,
weights.inf(dit)+PCLASS_FDS_WEIGHT*fds/max_afds);
}
// Calculate total weight.
double total=0;
forall_items(dit,weights) {
total += weights.inf(dit);
}
#ifdef PCLASS_DEBUG_MORE
cerr << "Finding pclass for " << vn.name << endl;
{
dic_item pit;
forall_items(pit,weights) {
tb_pclass *p = weights.key(pit);
cout << " " << p->name << " " << weights.inf(pit) << endl;
}
}
#endif
////
// We've now calculated the weights and the total weight. We
// will loop through all the classes until we find an acceptable
// one.
////
// Loop will break eventually.
tb_pnode *newpnode;
do {
newpnode = choose_pnode(weights,total,vn.type);
if (newpnode == NULL) {
// no available nodes
// need to free up a node and recalculate weights.
int pos = 0;
node ntor;
while (pos == 0) {
ntor = G.choose_node();
pos = G[ntor].posistion;
}
remove_node(ntor);
unassigned_nodes.insert(ntor,random());
continue;
break;
}
}
if (unassigned) unassigned_nodes.del(n);
newpos = pnode2posistion.access(newpnode);
} while (add_node(n,newpos) == 1);
newscore = get_score();
// This occurs when no pclass could be found.
if (newpnode == NULL) continue;
/* So it's negative if bad */
unassigned_nodes.del(n);
newscore = get_score();
// Negative means bad
scorediff = bestscore - newscore;
// tinkering aournd witht his.
if ((newscore < optimal) || (violated < bestviolated) ||
((violated == bestviolated) && (newscore < bestscore)) ||
......@@ -258,7 +401,6 @@ int assign()
absbestv = violated;
cycles_to_best = iters;
}
// if (newscore < 0.11f) {
if (newscore < optimal) {
timeend = used_time(timestart);
cout << "OPTIMAL ( " << optimal << ") in "
......@@ -266,15 +408,15 @@ int assign()
<< timeend << " seconds" << endl;
goto DONE;
}
} else { /* Reject this change */
// Accept change
} else {
// Reject change
remove_node(n);
if (oldpos != 0) {
int r = add_node(n,oldpos);
assert(r == 0);
add_node(n,oldpos);
}
}
}
temp *= temp_rate;
}
cout << "Done.\n";
......@@ -485,9 +627,11 @@ int main(int argc, char **argv)
dump_options("Configuration options:", options, noptions);
#endif
int seed = time(NULL)+getpid();
int seed;
if (getenv("ASSIGN_SEED") != NULL) {
sscanf(getenv("ASSIGN_SEED"),"%d",&seed);
} else {
seed = time(NULL)+getpid();
}
printf("seed = %d\n",seed);
srandom(seed);
......@@ -570,6 +714,24 @@ int main(int argc, char **argv)
#endif
}
}
node pn;
forall_nodes(pn,PG) {
pnode2node.insert(&PG[pn],pn);
}
for (int i=0;i<MAX_PNODES;++i) {
if (pnodes[i] != nil) {
pnode2posistion.insert(&PG[pnodes[i]],i);
}
}
cout << "Generating physical classes\n";
generate_pclasses(PG);
cout << "Nclasses: " << pclasses.length() << endl;
#ifdef PCLASS_DEBUG
pclass_debug();
#endif
cout << "Annealing!" << endl;
batch();
......
......@@ -15,7 +15,7 @@ static int init_temp = 100;
static int USE_OPTIMAL = 1;
static int temp_prob = 130;
static int temp_stop = 2;
static int CYCLES = 120;
static int CYCLES = 20;
static float temp_rate = 0.9;
static float opt_nodes_per_sw = 5.0;
......@@ -31,7 +31,7 @@ static float SCORE_UNASSIGNED = 1;/* Cost of an unassigned node*/
static float SCORE_OVER_BANDWIDTH = 0.5;/* Cost of going over bandwidth*/
static float SCORE_DESIRE = 1;/* Multiplier for desire costs*/
static float SCORE_FEATURE = 1;/* Multiplier for feature weights*/
static float SCORE_PCLASS = 0.5; /* Cost of each pclass */
static struct config_param options[] = {
{ "IT", CONFIG_INT, &init_temp, 0 },
......@@ -50,6 +50,7 @@ static struct config_param options[] = {
{ "DP", CONFIG_FLOAT, &SCORE_DIRECT_LINK_PENALTY, 0 },
{ "PN", CONFIG_FLOAT, &SCORE_PNODE, 0 },
{ "PP", CONFIG_FLOAT, &SCORE_PNODE_PENALTY, 0 },
{ "PC", CONFIG_FLOAT, &SCORE_PCLASS, 0 },
{ "SW", CONFIG_FLOAT, &SCORE_SWITCH, 0 },
{ "ON", CONFIG_FLOAT, &opt_nodes_per_sw, 0 },
{ "TR", CONFIG_FLOAT, &temp_rate, 0 }
......
#include <LEDA/graph_alg.h>
#include <LEDA/graphwin.h>
#include <LEDA/ugraph.h>
#include <LEDA/dictionary.h>
#include <LEDA/map.h>
#include <LEDA/graph_iterator.h>
#include <LEDA/node_pq.h>
#include <LEDA/sortseq.h>
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <string.h>
#include <assert.h>
#include "common.h"
#include "physical.h"
#include "virtual.h"
#include "pclass.h"
extern node pnodes[MAX_PNODES];
extern dictionary<tb_pnode*,node> pnode2node;
extern pclass_list pclasses;
extern dictionary<string,pclass_list*> type_table;
typedef two_tuple<node,int> link_info; // dst, bw
// returns 1 if a and b are equivalent. They are equivalent if the
// type and features information match and if there is a one-to-one
// mapping between links that preserves bw, and destination.
int pclass_equiv(tb_pgraph &PG, tb_pnode *a,tb_pnode *b)
{
// check type information
dic_item it;
forall_items(it,a->types) {
dic_item bit;
bit = b->types.lookup(a->types.key(it));
if (bit == nil)
return 0;
if (b->types[bit] != a->types.inf(it))
return 0;
}
// check features
seq_item sit;
forall_items(sit,a->features) {
seq_item bit;
bit = b->features.lookup(a->features.key(sit));
if (bit == nil)
return 0;
if (b->features[bit] != a->features.inf(sit))
return 0;
}
// check links - to do this we first create sets of every link in b.
// we then loop through every link in a, find a match in the set, and
// remove it from the set.
node an = pnode2node.access(a);
node bn = pnode2node.access(b);
// yet another place where we'd like to use sets <sigh>
list<link_info> b_links;
edge e;
forall_inout_edges(e,bn) {
node dst=PG.target(e);
if (dst == bn)
dst = PG.source(e);
b_links.push_front(link_info(dst,PG[e].bandwidth));
}
forall_inout_edges(e,an) {
link_info link;
node dst=PG.target(e);
if (dst == an)
dst = PG.source(e);
int bw = PG[e].bandwidth;
bool found = 0;
list_item lit;
forall_items(lit,b_links) {
link=b_links.inf(lit);
if ((dst == link.first()) && (bw == link.second())) {
found = 1;
b_links.del(lit);
break;
}
}
if (found == 0) return 0;
}
return 1;
}
/* This function takes a physical graph and generates an set of
equivalence classes of physical nodes. The globals pclasses (a
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) {
node cur;
dictionary<tb_pclass*,tb_pnode*> canonical_members;
forall_nodes(cur, PG) {
tb_pclass *curclass;
bool found_class = 0;
tb_pnode *curP = &PG[cur];
dic_item dit;
forall_items(dit,canonical_members) {
curclass=canonical_members.key(dit);
if (pclass_equiv(PG,curP,canonical_members[dit])) {
// found the right class
found_class=1;
curclass->add_member(curP);
break;
}
}
if (found_class == 0) {
// new class
tb_pclass *n = new tb_pclass;
pclasses.push_back(n);
canonical_members.insert(n,curP);
n->name = curP->name;
n->add_member(curP);
}
}
list_item it;
forall_items(it,pclasses) {
tb_pclass *cur = pclasses[it];
dic_item dit;
forall_items(dit,cur->members) {
if (type_table.lookup(cur->members.key(dit)) == nil) {
type_table.insert(cur->members.key(dit),new pclass_list);
}
type_table.access(cur->members.key(dit))->push_back(cur);
}
}
return 0;
}
int tb_pclass::add_member(tb_pnode *p)
{
dic_item it;
forall_items(it,p->types) {
string type = p->types.key(it);
if (members.lookup(type) == nil) {
members.insert(type,new tb_pnodelist);
}
members.access(type)->push_back(p);
}
size++;
p->my_class=this;
return 0;
}
// should be called after add_node
int pclass_set(tb_vnode &v,tb_pnode &p)
{
tb_pclass *c = p.my_class;
// remove p node from correct lists in equivalence class.
dic_item dit;
forall_items(dit,c->members) {
if (c->members.key(dit) == p.current_type) {
// same class - only remove if node is full
if (p.current_load == p.max_load) {
(c->members.inf(dit))->remove(&p);
}
} else {
// If it's not in the list then this fails quietly.
(c->members.inf(dit))->remove(&p);
}
}
c->used += 1.0/(p.max_load);
return 0;
}
int pclass_unset(tb_pnode &p)
{
// add pnode to all lists in equivalence class.
tb_pclass *c = p.my_class;
dic_item dit;
forall_items(dit,c->members) {
if (c->members.key(dit) == 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 (! (c->members.inf(dit))->exists(&p)) {
assert(p.current_load > 0);
if (p.current_load == 1) {
(c->members.inf(dit))->push_back(&p);
} else {
(c->members.inf(dit))->push_front(&p);
}
}
} else {
(c->members.inf(dit))->push_back(&p);
}
}
c->used -= 1.0/(p.max_load);
return 0;
}
void pclass_debug()
{
cout << "PClasses:\n";
list_item it;
forall_items(it,pclasses) {
tb_pclass *p = pclasses[it];
cout << p->name << ": size = " << p->size << "\n";
dic_item dit;
forall_items(dit,p->members) {
cout << " " << p->members.key(dit) << ":\n";
list_item lit;
const list<tb_pnode*> L = p->members.inf(dit)->L;
forall_items(lit,L) {
cout << " " << L.inf(lit)->name << "\n";
}
}
cout << "\n";
}
cout << "\n";
cout << "Type Table:\n";
dic_item dit;
forall_items(dit,type_table) {
cout << type_table.key(dit) << ":";
list_item lit;
pclass_list *L = type_table.inf(dit);
forall_items(lit,*L) {
cout << " " << (L->inf(lit))->name;
}
cout << "\n";
}
}
int compare(tb_pclass *const &a, tb_pclass *const &b)
{
if (a==b) return 0;
if (a < b) return -1;
return 1;
}
#ifndef __PCLASS_H
#define __PCLASS_H
// tb pnode list is a data structure that acts like list but has
// O(1) removal. It is a list of tb_pnode*.
class tb_pnodelist {
public:
list<tb_pnode*> L;
dictionary<tb_pnode*,list_item> D;
list_item push_front(tb_pnode *p) {
list_item it = L.push_front(p);
D.insert(p,it);
return it;
};
list_item push_back(tb_pnode *p) {
list_item it = L.push_back(p);
D.insert(p,it);
return it;
};
int remove(tb_pnode *p) {
if (exists(p)) {
list_item it = D.access(p);
L.del_item(it);
D.del(p);
return 0;
} else {
return 1;
}
};
int exists(tb_pnode *p) {
return (D.lookup(p) != nil);
}
tb_pnode *front() {
list_item it = L.first();
return ((it == nil) ? NULL : L[it]);
};
};
class tb_pclass {
public:
tb_pclass() {
size=0;
used=0;
}
int add_member(tb_pnode *p);
string name; // purely for debugging
int size;
double used;
dictionary<string,tb_pnodelist*> members;
};
typedef list<tb_pclass*> pclass_list;
/* Constants */
#define PCLASS_BASE_WEIGHT 1
#define PCLASS_NEIGHBOR_WEIGHT 1
#define PCLASS_UTIL_WEIGHT 1
#define PCLASS_FDS_WEIGHT 2
/* routines defined in pclass.cc */
int generate_pclasses(tb_pgraph &PG);// sets pclasses and type_table globals
/* The following two routines sets and remove mappings in pclass
datastructures */
int pclass_set(tb_vnode &v,tb_pnode &p);
int pclass_unset(tb_pnode &p);
void pclass_debug(); // dumps debug info
int compare(tb_pclass *const &, tb_pclass *const &);
#endif
#ifndef __PHYSICAL_H
#define __PHYSICAL_H
class tb_pclass;
class tb_pnode {
public:
tb_pnode() {;}
......@@ -14,7 +16,7 @@ public:
{
return i;
}
dictionary<string,int> types; // contains max nodes for each type
sortseq<string,double> features; // contains cost of each feature
string current_type;
......@@ -27,6 +29,8 @@ public:
int pnodes_used; // for switch nodes
node sgraph_switch; // only for switches, the corresponding
// sgraph switch.
tb_pclass *my_class;
};
class tb_switch {
......@@ -83,4 +87,5 @@ public:
typedef GRAPH<tb_pnode,tb_plink> tb_pgraph;
typedef UGRAPH<tb_switch,tb_slink> tb_sgraph;
int compare(tb_pnode *const &a, tb_pnode *const &b);
#endif
......@@ -25,9 +25,11 @@
#include <string.h>
#include "common.h"
#include "score.h"
#include "virtual.h"
#include "physical.h"
#include "pclass.h"
#include "score.h"
#include "assert.h"
......@@ -95,6 +97,7 @@ void init_score()
forall_edges(e,G) {
tb_vlink &ve=G[e];
ve.type=tb_vlink::LINK_UNKNOWN;
ve.plink=NULL;
}
forall_nodes(n,PG) {
tb_pnode &pn=PG[n];
......@@ -133,9 +136,18 @@ void remove_node(node n)
fprintf(stderr," no_connections = %d\n",vnoder.no_connections);
#endif