games/galaxy: parallelize gravitational force calculations

Once the Barnes-Hut tree is constructed, the gravitational
force calculations can be done in parallel by dividing the
bodies up between a number of procs.
This commit is contained in:
spew 2017-03-25 13:05:47 -05:00
parent 4e8494aad7
commit 85b8d253d4
7 changed files with 202 additions and 88 deletions

View file

@ -102,23 +102,11 @@ Exit the simulator.
Certain aspects of the galaxy simulator are controlled by Certain aspects of the galaxy simulator are controlled by
the following options: the following options:
.TP .TP
.BI -t " throttle"
Causes the process that calculates forces to relinquish
the processor for
.I throttle
milliseconds after each calculation.
.TP
.BI -G " gravity" .BI -G " gravity"
Sets the gravitational constant to Sets the gravitational constant to
.I gravity. .I gravity.
The default value is 1. The default value is 1.
.TP .TP
.BI -ε " softening"
Sets the
.I softening
factor to prevent gravitational singularities during
collisions or near-collisions. The default value is 500.
.TP
.BI -f " file" .BI -f " file"
Reads the galaxy file Reads the galaxy file
.I file .I file
@ -127,6 +115,25 @@ Reads the galaxy file
.TP .TP
.B -i .B -i
Reads a galaxy file from standard input. Reads a galaxy file from standard input.
.TP
.BI -p " procs"
Specifies the number of extra processes to use in order
to calculate the gravitational force on each body in
parallel.
The default value is
.BR $NPROC-1 .
.TP
.BI -t " throttle"
Causes the process that calculates forces to relinquish
the processor for
.I throttle
milliseconds after each calculation.
.TP
.BI -ε " softening"
Sets the
.I softening
factor to prevent gravitational singularities during
collisions or near-collisions. The default value is 500.
.SS Mkgalaxy .SS Mkgalaxy
.I Mkgalaxy .I Mkgalaxy
is a utility to create galaxies for simulation. is a utility to create galaxies for simulation.

View file

@ -11,7 +11,7 @@ glxyinit(void)
glxy.a = calloc(5, sizeof(Body)); glxy.a = calloc(5, sizeof(Body));
if(glxy.a == nil) if(glxy.a == nil)
sysfatal("could not allocate glxy: %r"); sysfatal("could not allocate glxy: %r");
glxy.l = 0; glxy.nb = 0;
glxy.max = 5; glxy.max = 5;
} }
@ -20,13 +20,13 @@ body(void)
{ {
Body *b; Body *b;
if(glxy.l == glxy.max) { if(glxy.nb == glxy.max) {
glxy.max *= 2; glxy.max *= 2;
glxy.a = realloc(glxy.a, sizeof(Body) * glxy.max); glxy.a = realloc(glxy.a, sizeof(Body) * glxy.max);
if(glxy.a == nil) if(glxy.a == nil)
sysfatal("could not realloc glxy: %r"); sysfatal("could not realloc glxy: %r");
} }
b = glxy.a + glxy.l++; b = glxy.a + glxy.nb++;
*b = ZB; *b = ZB;
return b; return b;
} }
@ -64,11 +64,11 @@ center(void)
Vector gc, gcv; Vector gc, gcv;
double mass; double mass;
if(glxy.l == 0) if(glxy.nb == 0)
return (Vector){0, 0}; return (Vector){0, 0};
gc.x = gc.y = gcv.x = gcv.y = mass = 0; gc.x = gc.y = gcv.x = gcv.y = mass = 0;
for(b = glxy.a; b < glxy.a+glxy.l; b++) { for(b = glxy.a; b < glxy.a+glxy.nb; b++) {
gc.x += b->x * b->mass; gc.x += b->x * b->mass;
gc.y += b->y * b->mass; gc.y += b->y * b->mass;
gcv.x += b->v.x * b->mass; gcv.x += b->v.x * b->mass;
@ -79,7 +79,7 @@ center(void)
gc.y /= mass; gc.y /= mass;
gcv.x /= mass; gcv.x /= mass;
gcv.y /= mass; gcv.y /= mass;
for(b = glxy.a; b < glxy.a+glxy.l; b++) { for(b = glxy.a; b < glxy.a+glxy.nb; b++) {
b->x -= gc.x; b->x -= gc.x;
b->y -= gc.y; b->y -= gc.y;
b->v.x -= gcv.x; b->v.x -= gcv.x;
@ -145,7 +145,7 @@ readglxy(int fd)
int cmd, len; int cmd, len;
Body *b; Body *b;
glxy.l = 0; glxy.nb = 0;
Binit(&bin, fd, OREAD); Binit(&bin, fd, OREAD);
for(;;) { for(;;) {
line = Brdline(&bin, ' '); line = Brdline(&bin, ' ');
@ -212,7 +212,7 @@ writeglxy(int fd)
Bprint(&bout, "DT %g\n", dt); Bprint(&bout, "DT %g\n", dt);
Bprint(&bout, "GRAV %g\n", G); Bprint(&bout, "GRAV %g\n", G);
for(b = glxy.a; b < glxy.a + glxy.l; b++) for(b = glxy.a; b < glxy.a + glxy.nb; b++)
Bprint(&bout, "%B\n", b); Bprint(&bout, "%B\n", b);
Bterm(&bout); Bterm(&bout);

View file

@ -46,7 +46,6 @@ Cursor pausecursor={
}; };
enum { enum {
STK = 8192,
DOBODY = 0, DOBODY = 0,
SPEED, SPEED,
GRAV, GRAV,
@ -68,7 +67,7 @@ double
LIM = 10, LIM = 10,
dt²; dt²;
char *file; char *file;
int showv, showa, throttle, paused; int showv, showa, paused;
char *menustr[] = { char *menustr[] = {
[DOBODY] "new body", [DOBODY] "new body",
@ -149,7 +148,7 @@ drawstats(void)
Point p; Point p;
static char buf[1024]; static char buf[1024];
snprint(buf, sizeof(buf), "Number of bodies: %d", glxy.l); snprint(buf, sizeof(buf), "Number of bodies: %d", glxy.nb);
p = addpt(screen->r.min, (Point){5, 3}); p = addpt(screen->r.min, (Point){5, 3});
string(screen, p, display->white, ZP, font, buf); string(screen, p, display->white, ZP, font, buf);
@ -170,7 +169,7 @@ drawglxy(void)
int s; int s;
draw(screen, screen->r, display->black, 0, ZP); draw(screen, screen->r, display->black, 0, ZP);
for(b = glxy.a; b < glxy.a + glxy.l; b++) { for(b = glxy.a; b < glxy.a + glxy.nb; b++) {
pos = topoint(b->Vector); pos = topoint(b->Vector);
s = b->size/scale; s = b->size/scale;
fillellipse(screen, pos, s, s, b->col, ZP); fillellipse(screen, pos, s, s, b->col, ZP);
@ -529,57 +528,6 @@ kbdthread(void*)
} }
} }
/* verlet barnes-hut */
void
simulate(void*)
{
Body *b;
double f;
threadsetname("simulate");
for(;;) {
qlock(&glxy);
if(throttle)
sleep(throttle);
drawglxy();
Again:
space.t = EMPTY;
quads.l = 0;
STATS(quaddepth = 0;)
for(b = glxy.a; b < glxy.a + glxy.l; b++) {
if(quadins(b, LIM) == -1) {
growquads();
goto Again;
}
}
STATS(avgcalcs = 0;)
for(b = glxy.a; b < glxy.a + glxy.l; b++) {
b->a.x = b->newa.x;
b->a.y = b->newa.y;
b->newa.x = b->newa.y = 0;
STATS(calcs = 0;)
quadcalc(b, space, LIM);
STATS(avgcalcs += calcs;)
}
STATS(avgcalcs /= glxy.l;)
for(b = glxy.a; b < glxy.a + glxy.l; b++) {
b->x += dt*b->v.x + dt²*b->a.x/2;
b->y += dt*b->v.y + dt²*b->a.y/2;
b->v.x += dt*(b->a.x + b->newa.x)/2;
b->v.y += dt*(b->a.y + b->newa.y)/2;
CHECKLIM(b, f);
}
qunlock(&glxy);
}
}
Vector Vector
tovector(Point p) tovector(Point p)
{ {
@ -600,6 +548,7 @@ usage(void)
void void
threadmain(int argc, char **argv) threadmain(int argc, char **argv)
{ {
char* nproc;
int doload; int doload;
doload = 0; doload = 0;
@ -607,6 +556,9 @@ threadmain(int argc, char **argv)
default: default:
usage(); usage();
break; break;
case 'p':
extraproc = strtol(EARGF(usage()), nil, 0);
break;
case 't': case 't':
throttle = strtol(EARGF(usage()), nil, 0); throttle = strtol(EARGF(usage()), nil, 0);
break; break;
@ -637,6 +589,16 @@ threadmain(int argc, char **argv)
sysfatal("threadmain: could not open file: %r"); sysfatal("threadmain: could not open file: %r");
} }
if(extraproc < 0) {
nproc = getenv("NPROC");
if(nproc == nil)
extraproc = 0;
else
extraproc = strtol(nproc, nil, 10) - 1;
if(extraproc < 0)
extraproc = 0;
}
if(initdraw(nil, nil, "Galaxy") < 0) if(initdraw(nil, nil, "Galaxy") < 0)
sysfatal("initdraw failed: %r"); sysfatal("initdraw failed: %r");
if(mc = initmouse(nil, screen), mc == nil) if(mc = initmouse(nil, screen), mc == nil)

View file

@ -33,7 +33,7 @@ struct Quad {
struct { struct {
QLock; QLock;
Body *a; Body *a;
int l, max; int nb, max;
} glxy; } glxy;
struct { struct {
@ -49,11 +49,6 @@ enum {
BODY, BODY,
}; };
Point orig;
double G, θ, scale, ε, LIM, dt, dt²;
Body ZB;
QB space;
Image *randcol(void); Image *randcol(void);
Point topoint(Vector); Point topoint(Vector);
Vector tovector(Point); Vector tovector(Point);
@ -65,16 +60,24 @@ void glxyinit(void);
int Bfmt(Fmt*); int Bfmt(Fmt*);
void readglxy(int); void readglxy(int);
void writeglxy(int); void writeglxy(int);
void drawglxy(void);
void simulate(void*);
void quadcalc(Body*, QB, double); void quadcalc(Body*, QB, double);
int quadins(Body*, double); int quadins(Body*, double);
void growquads(void); void growquads(void);
void quadsinit(void); void quadsinit(void);
int stats; int stats, quaddepth, calcs, extraproc, throttle;
int quaddepth; double G, θ, scale, ε, LIM, dt, dt², avgcalcs;
int calcs; Point orig;
double avgcalcs; QB space;
Body ZB;
enum {
STK = 8192,
};
#define STATS(e) if(stats) {e} #define STATS(e) if(stats) {e}

View file

@ -2,7 +2,7 @@
TARG=galaxy mkgalaxy TARG=galaxy mkgalaxy
BIN=/$objtype/bin/games BIN=/$objtype/bin/games
OGALAXY=galaxy.$O quad.$O body.$O OGALAXY=galaxy.$O quad.$O body.$O simulate.$O
OMKGALAXY=mkgalaxy.$O body.$O OMKGALAXY=mkgalaxy.$O body.$O
</sys/src/cmd/mkmany </sys/src/cmd/mkmany

View file

@ -152,7 +152,7 @@ main(int argc, char **argv)
if(argc != 1) if(argc != 1)
usage(); usage();
new = glxy.l; new = glxy.nb;
lim = strtod(*argv, nil); lim = strtod(*argv, nil);
mkbodies(lim); mkbodies(lim);
@ -160,7 +160,7 @@ main(int argc, char **argv)
for(b = glxy.a; b < glxy.a + new; b++) for(b = glxy.a; b < glxy.a + new; b++)
Bprint(&bout, "%B\n", b); Bprint(&bout, "%B\n", b);
for(b = glxy.a+new; b < glxy.a+glxy.l; b++) { for(b = glxy.a+new; b < glxy.a+glxy.nb; b++) {
b->x += o.x; b->x += o.x;
b->y += o.y; b->y += o.y;
Bprint(&bout, "%B\n", b); Bprint(&bout, "%B\n", b);

View file

@ -0,0 +1,142 @@
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include "galaxy.h"
int extraproc = -1, throttle;
static QLock* golock;
static Rendez* gorend;
static int* go;
static QLock runninglock;
static Rendez runningrend;
static int running;
static void
calcproc(void *v)
{
Body *b, *e;
int nbody;
int pid;
pid = (uintptr)v;
for(;;) {
qlock(golock+pid);
while(!go[pid])
rsleep(gorend+pid);
go[pid] = 0;
qunlock(golock+pid);
nbody = glxy.nb / (extraproc+1);
b = glxy.a + nbody * pid;
e = b + nbody;
while(b < e) {
b->a.x = b->newa.x;
b->a.y = b->newa.y;
b->newa.x = b->newa.y = 0;
quadcalc(b++, space, LIM);
}
qlock(&runninglock);
if(--running == 0)
rwakeup(&runningrend);
qunlock(&runninglock);
}
}
static void
startprocs(void)
{
int pid;
golock = calloc(extraproc, sizeof(*golock));
if(golock == nil)
sysfatal("Could not create go locks: %r\n");
gorend = calloc(extraproc, sizeof(*gorend));
if(gorend == nil)
sysfatal("Could not create go rendez: %r\n");
go = calloc(extraproc, sizeof(*go));
if(go == nil)
sysfatal("Could not create go flags: %r\n");
for(pid = 0; pid < extraproc; pid++)
gorend[pid].l = golock+pid;
runningrend.l = &runninglock;
for(pid = 0; pid < extraproc; pid++)
proccreate(calcproc, (void*)pid, STK);
}
/* verlet barnes-hut */
void
simulate(void*)
{
Body *b, *s;
int nbody, pid;
double f;
threadsetname("simulate");
startprocs();
for(;;) {
qlock(&glxy);
if(throttle)
sleep(throttle);
drawglxy();
Again:
space.t = EMPTY;
quads.l = 0;
STATS(quaddepth = 0;)
for(b = glxy.a; b < glxy.a + glxy.nb; b++) {
if(quadins(b, LIM) == -1) {
growquads();
goto Again;
}
}
running = extraproc;
for(pid = 0; pid < extraproc; pid++) {
qlock(golock+pid);
go[pid] = 1;
rwakeup(gorend+pid);
qunlock(golock+pid);
}
STATS(avgcalcs = 0;)
nbody = glxy.nb / (extraproc+1);
s = glxy.a + nbody * (extraproc);
for(b = s; b < glxy.a + glxy.nb; b++) {
b->a.x = b->newa.x;
b->a.y = b->newa.y;
b->newa.x = b->newa.y = 0;
STATS(calcs = 0;)
quadcalc(b, space, LIM);
STATS(avgcalcs += calcs;)
}
STATS(avgcalcs /= glxy.nb;)
qlock(&runninglock);
while(running > 0)
rsleep(&runningrend);
qunlock(&runninglock);
for(b = glxy.a; b < glxy.a + glxy.nb; b++) {
b->x += dt*b->v.x + dt²*b->a.x/2;
b->y += dt*b->v.y + dt²*b->a.y/2;
b->v.x += dt*(b->a.x + b->newa.x)/2;
b->v.y += dt*(b->a.y + b->newa.y)/2;
CHECKLIM(b, f);
}
qunlock(&glxy);
}
}