Commit 01a47460 authored by Emden R Gansner's avatar Emden R Gansner

Fix the tred algorithm for better performance, making it roughly quadratic.

Also replace the recursive DFS with an explicit stack to prevent stack overflow.
The latter is probably not necessary, as real-life DAGs shouldn't have that many layers.
parent b91faf63
......@@ -89,6 +89,7 @@ ccomps.1.pdf: $(srcdir)/ccomps.1
tred_SOURCES = tred.c
tred_LDADD = \
$(top_builddir)/lib/common/libcommon_C.la \
$(top_builddir)/lib/ingraphs/libingraphs_C.la \
$(top_builddir)/lib/cgraph/libcgraph.la
......
......@@ -25,13 +25,20 @@
#include "config.h"
#include "cgraph.h"
#include "arith.h"
#include "timing.h"
#include <stdlib.h>
#define NEW(t) (t*)malloc(sizeof(t))
#define N_NEW(n,t) (t*)malloc((n)*sizeof(t))
typedef struct {
Agrec_t h;
int mark;
} Agnodeinfo_t;
unsigned char on_stack;
unsigned char dist;
} nodeinfo_t;
#define ON_STACK(ninfo,n) (ninfo[AGSEQ(n)].on_stack)
#define DIST(ninfo,n) (ninfo[AGSEQ(n)].dist)
#define agrootof(n) ((n)->root)
#ifdef HAVE_UNISTD_H
......@@ -41,45 +48,184 @@ typedef struct {
#include <getopt.h>
char **Files;
char *CmdName;
#define MARK(n) (((Agnodeinfo_t*)(n->base.data))->mark)
static char **Files;
static char *CmdName;
static int Verbose;
typedef struct blk_t {
Agedge_t **data;
Agedge_t **endp;
struct blk_t *prev;
struct blk_t *next;
} blk_t;
typedef struct {
blk_t *fstblk;
blk_t *curblk;
Agedge_t **curp;
} stk_t;
#define SMALLBUF 1024
#define BIGBUF 1000000
static int dfs(Agnode_t * n, Agedge_t * link, int warn)
typedef struct {
Agedge_t *base[SMALLBUF];
blk_t Blk;
stk_t Stk;
} estack_t;
static void initStk(estack_t* sp)
{
Agedge_t *e;
Agedge_t *f;
Agraph_t *g = agrootof(n);
sp->Blk.data = sp->base;
sp->Blk.endp = sp->Blk.data + SMALLBUF;
sp->Stk.curblk = sp->Stk.fstblk = &(sp->Blk);
sp->Stk.curp = sp->Stk.curblk->data;
}
MARK(n) = 1;
static void push(estack_t* sp, Agedge_t * ep, nodeinfo_t* ninfo)
{
if (sp->Stk.curp == sp->Stk.curblk->endp) {
if (sp->Stk.curblk->next == NULL) {
blk_t *bp = NEW(blk_t);
if (bp == 0) {
fprintf(stderr, "gc: Out of memory\n");
exit(1);
}
bp->prev = sp->Stk.curblk;
bp->next = NULL;
bp->data = N_NEW(BIGBUF, Agedge_t *);
if (bp->data == 0) {
fprintf(stderr, "gc: Out of memory\n");
exit(1);
}
bp->endp = bp->data + BIGBUF;
sp->Stk.curblk->next = bp;
}
sp->Stk.curblk = sp->Stk.curblk->next;
sp->Stk.curp = sp->Stk.curblk->data;
}
ON_STACK(ninfo, aghead(ep)) = 1;
*sp->Stk.curp++ = ep;
}
for (e = agfstin(g, n); e; e = f) {
f = agnxtin(g, e);
if (e == link)
continue;
if (MARK(agtail(e)))
agdelete(g, e);
static Agedge_t *pop(estack_t* sp, nodeinfo_t* ninfo)
{
Agedge_t* e;
if (sp->Stk.curp == sp->Stk.curblk->data) {
if (sp->Stk.curblk == sp->Stk.fstblk)
return 0;
sp->Stk.curblk = sp->Stk.curblk->prev;
sp->Stk.curp = sp->Stk.curblk->endp;
}
sp->Stk.curp--;
e = *sp->Stk.curp;
ON_STACK(ninfo,aghead(e)) = 0;
return e;
}
static Agedge_t *top(estack_t* sp)
{
Agedge_t** pp;
if (sp->Stk.curp == sp->Stk.curblk->data) {
if (sp->Stk.curblk == sp->Stk.fstblk)
return 0;
pp = sp->Stk.curblk->prev->endp-1;
}
else
pp = sp->Stk.curp-1;
return *pp;
}
/* dfs:
* Main function for transitive reduction.
* This does a DFS starting at node n. Each node records the length of
* its largest simple path from n. We only care if the length is > 1. Node
* n will have distance 0; outneighbors of n will have distance 1 or 2; all
* others will have distance 2.
*
* During the DFS, we only push edges on the stack whose head has distance 0
* (i.e., hasn't been visited yet), setting its distance to the distance of the
* tail node plus one. If we find a head node with distance 1, we don't push the
* edge, since it has already been in a DFS, but we update its distance. We also
* check for back edges and report these.
*
* After the DFS, we check all outedges of n. Those edges whose head has
* distance 2 we delete. We also delete all but one copy of any edges with the
* same head.
*/
static int dfs(Agnode_t * n, nodeinfo_t* ninfo, int warn, estack_t* sp)
{
Agraph_t *g = agrootof(n);
Agedgepair_t dummy;
Agedge_t* link;
Agedge_t* next;
Agedge_t* prev;
Agedge_t* e;
Agedge_t* f;
Agnode_t* v;
Agnode_t* hd;
Agnode_t* oldhd;
for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
if (MARK(aghead(e))) {
if (!warn) {
warn++;
fprintf(stderr,
dummy.out.base.tag.objtype = AGOUTEDGE;
dummy.out.node = n;
dummy.in.base.tag.objtype = AGINEDGE;
dummy.in.node = NULL;
push(sp, &dummy.out, ninfo);
prev = 0;
while ((link = top(sp))) {
v = aghead(link);
if (prev)
next = agnxtout(g, prev);
else
next = agfstout(g, v);
for (; next; next = agnxtout(g, next)) {
hd = aghead(next);
if (hd == v) continue; // Skip a loop
if (ON_STACK(ninfo,hd)) {
if (!warn) {
warn++;
fprintf(stderr,
"warning: %s has cycle(s), transitive reduction not unique\n",
agnameof(g));
fprintf(stderr, "cycle involves edge %s -> %s\n",
agnameof(agtail(e)), agnameof(aghead(e)));
fprintf(stderr, "cycle involves edge %s -> %s\n",
agnameof(v), agnameof(hd));
}
}
else if (DIST(ninfo,hd) == 0) {
DIST(ninfo,hd) = MIN(1,DIST(ninfo,v))+1;
break;
}
} else
warn = dfs(aghead(e), AGOUT2IN(e), warn);
else if (DIST(ninfo,hd) == 1) {
DIST(ninfo,hd) = MIN(1,DIST(ninfo,v))+1;
}
}
if (next) {
push(sp, next, ninfo);
prev = 0;
}
else {
prev = pop(sp, ninfo);
}
}
oldhd = NULL;
for (e = agfstout(g, n); e; e = f) {
f = agnxtout(g, e);
hd = aghead(e);
if (oldhd == hd)
agdelete(g, e);
else {
oldhd = hd;
if (DIST(ninfo, hd)>1) agdelete(g, e);
}
}
MARK(n) = 0;
return warn;
}
static char *useString = "Usage: %s [-?] <files>\n\
static char *useString = "Usage: %s [-v?] <files>\n\
-v - verbose\n\
-? - print usage\n\
If no files are specified, stdin is used\n";
......@@ -95,8 +241,11 @@ static void init(int argc, char *argv[])
CmdName = argv[0];
opterr = 0;
while ((c = getopt(argc, argv, ":")) != -1) {
while ((c = getopt(argc, argv, "v")) != -1) {
switch (c) {
case 'v':
Verbose = 1;
break;
case '?':
if (optopt == '?')
usage(0);
......@@ -113,15 +262,39 @@ static void init(int argc, char *argv[])
Files = argv;
}
static void process(Agraph_t * g)
/* process:
* Do a DFS for each vertex in graph g, so the time
* complexity is O(|V||E|).
*/
static void process(Agraph_t * g, estack_t* sp)
{
Agnode_t *n;
int cnt = 0;
int warn = 0;
double secs;
double total_secs = 0;
nodeinfo_t* ninfo;
size_t infosize;
aginit(g, AGNODE, "info", sizeof(Agnodeinfo_t), TRUE);
infosize = (agnnodes(g)+1)*sizeof(nodeinfo_t);
ninfo = (nodeinfo_t*)malloc(infosize);
if (Verbose)
fprintf(stderr, "Processing graph %s\n", agnameof(g));
for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
warn = dfs(n, 0, warn);
memset(ninfo, 0, infosize);
if (Verbose) start_timer();
warn = dfs(n, ninfo, warn, sp);
if (Verbose) {
secs = elapsed_sec();
total_secs += secs;
cnt++;
if ((cnt%1000) == 0) fprintf (stderr, "[%d]\n", cnt);
}
}
if (Verbose)
fprintf(stderr, "Finished graph %s: %.02f secs.\n", agnameof(g), total_secs);
free (ninfo);
agwrite(g, stdout);
fflush(stdout);
}
......@@ -135,13 +308,15 @@ int main(int argc, char **argv)
{
Agraph_t *g;
ingraph_state ig;
estack_t estk;
init(argc, argv);
newIngraph(&ig, Files, gread);
initStk(&estk);
while ((g = nextGraph(&ig)) != 0) {
if (agisdirected(g))
process(g);
process(g, &estk);
agclose(g);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment