Scalable Tissue Imaging and Modeling (STIM) Laboratory
Department of Electrical and Computer Engineering
Cullen College of Engineering
University of Houston
David Mayerich
STIM Laboratory, University of Houston
Constants: thermal conductivity \(k\), density \(\rho\), and heat capacity \(c\)
\(T\) is the temperature measured at point \((x,y)\) at time \(t\)
Laplace operator (Laplacian):
David Mayerich
STIM Laboratory, University of Houston
change in temperature over time
material properties
heat flow along both directions
Track the discrete temperature on a grid:
David Mayerich
STIM Laboratory, University of Houston
Each discrete spatial location \((x_i,y_j)\) stores the temperature at time \(t_n\)
Spacing between grid points is consistent (but doesn't have to be):
\(\Delta_x = x_{i+1}-x_i \quad\quad \Delta_y = y_{j+1}-y_j\)
We update this grid to calculate the temperature at each time step:
\(\Delta_t = t_{n+1}-t_n\)
Step values are specified by the user
Related to actual spatial locations and time points, for example:
\(x_i = x_0 + i\cdot \Delta_x\)
Start from a first-order Taylor series:
David Mayerich
STIM Laboratory, University of Houston
Given two known points \(c=x_i\) and \(x=x_{i+1}=x_i + x_\Delta\):
Solve for \(f'(x)\):
Note that the order in the error term is reduced:
Finite difference methods are used to approximate a partial derivative from samples of a function:
David Mayerich
STIM Laboratory, University of Houston
Given these approximations for the spatial partial derivatives, we want to compute \(T\) at \(t_{n+1}\) based on the diffusion equation:
Approximating the second derivative:
Given the temperature \(T^n_{i,j}\) at \(t_n\), we approximate the future temperature \(T^{n+1}_{i,j}\) at \(t_{n+1}\) using the forward finite difference:
David Mayerich
STIM Laboratory, University of Houston
Using the same finite difference methods, we can approximate the two spatial derivatives:
by solving for \(T^{n+1}\):
which depends on the partial differential equation we already know:
Assuming \(h=\Delta_x = \Delta_y\) and collecting terms, we have:
Euler's Method: solves a PDE using a first-order (Taylor Series) approximation
Given the value of \(T^n_{i,j}=T(x_i, y_j, t_n)\)
Approximate the PDE at every point:
David Mayerich
STIM Laboratory, University of Houston
Update every point to the next time step:
Finite difference methods are based on Taylor series:
\(f(x)=f(c)+f'(c)(x-c)+O(\Delta_x)\)
where \(c=x_i\) and \(x=x_{i+1}\):
\(f(x_{i+1})=f(x)+f'(x_i)\Delta_x + O(\Delta_x)\)
solve for the derivative:
\(f'(x_i)=\frac{f(x_{i+1}) - f(x)}{\Delta_x} + O(\Delta_x)\)
Second derivative error:
David Mayerich
STIM Laboratory, University of Houston
Error term depends on sample spacing: \(E = O(\Delta_x^2)\)
Smaller samples provide better accuracy but increase compute time
Stability is based on the Courant-Friedrichs-Lewy (CFL) condition:
Approximation
Error and Stability
Select \(\Delta_x\), \(\Delta_y\), and \(\Delta_t\) to optimize accuracy and minimize compute time
David Mayerich
STIM Laboratory, University of Houston
void heat(float* A, float alpha, float h, float dt, int S, float epsilon) {
}David Mayerich
STIM Laboratory, University of Houston
void heat(float* A, float alpha, float h, float dt, int S, float epsilon) {
float* B = new float[S * S]; // create a new array to store the output
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
}
}David Mayerich
STIM Laboratory, University of Houston
void heat(float* A, float alpha, float h, float dt, int S, float epsilon) {
float* B = new float[S * S]; // create a new array to store the output
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
float diff = 0.0f; // track how much the simulation result has changed
}
}David Mayerich
STIM Laboratory, University of Houston
void heat(float* A, float alpha, float h, float dt, int S, float epsilon) {
float* B = new float[S * S]; // create a new array to store the output
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
float diff = 0.0f; // track how much the simulation result has changed
// iterate through each pixel (why y then x?), note boundaries
for(int j=1; j<S; j++) {
for(int i=1; i<S; i++) {
}
}
}
}David Mayerich
STIM Laboratory, University of Houston
void heat(float* A, float alpha, float h, float dt, int S, float epsilon) {
float* B = new float[S * S]; // create a new array to store the output
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
float diff = 0.0f; // track how much the simulation result has changed
// iterate through each pixel (why y then x?), note boundaries
for(int j=1; j<S; j++) {
for(int i=1; i<S; i++) {
// calculate the partial derivative with respect to time (main calculation)
dT_dt = c * (A[j*S+i+1] + A[j*S+i-1] + A[(j+1)*S+i] + A[(j-1)*S+i] – 4*A[j*S+i];
B[j*S+i] = A[j*S+i] + dt * dT_dt; // use explicit integration
diff += fabs(B[j*S+i] - A[j*S+i]); // sum the absolute change
}
}
}
}David Mayerich
STIM Laboratory, University of Houston
void heat(float* A, float alpha, float h, float dt, int S, float epsilon) {
float* B = new float[S * S]; // create a new array to store the output
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
float diff = 0.0f; // track how much the simulation result has changed
// iterate through each pixel (why y then x?), note boundaries
for(int j=1; j<S; j++) {
for(int i=1; i<S; i++) {
// calculate the partial derivative with respect to time (main calculation)
dT_dt = c * (A[j*S+i+1] + A[j*S+i-1] + A[(j+1)*S+i] + A[(j-1)*S+i] – 4*A[j*S+i];
B[j*S+i] = A[j*S+i] + dt * dT_dt; // use explicit integration
diff += fabs(B[j*S+i] - A[j*S+i]); // sum the absolute change
}
}
// swap pointers
float* tmp = A; A = B; B = tmp;
// delete the temporary array and return
if(diff / (S*S) < epsilon) { delete B; return; }
}
}2X memory!
David Mayerich
STIM Laboratory, University of Houston
David Mayerich
STIM Laboratory, University of Houston
void heat(float* A, float alpha, float h, float dt, int S, float epsilon) {
float* B = new float[S * S]; // create a new array to store the output
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
float diff = 0.0f; // track how much the simulation result has changed
// iterate through each pixel (why y then x?), note boundaries
for(int j=1; j<S; j++) {
for(int i=1; i<S; i++) {
// calculate the partial derivative with respect to time (main calculation)
dT_dt = c * (A[j*S+i+1] + A[j*S+i-1] + A[(j+1)*S+i] + A[(j-1)*S+i] – 4*A[j*S+i];
B[j*S+i] = A[j*S+i] + dt * dT_dt; // use explicit integration
diff += fabs(B[j*S+i] - A[j*S+i]); // sum the absolute change
}
}
// swap pointers
float* tmp = A; A = B; B = tmp;
// delete the temporary array and return
if(diff / (S*S) < epsilon) { delete B; return; }
}
}David Mayerich
STIM Laboratory, University of Houston
void heat(float* A, float alpha, float h, float dt, int S, float epsilon) {
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
float diff = 0.0f; // track how much the simulation result has changed
// iterate through each pixel (why y then x?), note boundaries
for(int j=1; j<S; j++) {
for(int i=1; i<S; i++) {
// calculate the partial derivative with respect to time (main calculation)
float prev = A[j*S+i];
dT_dt = c * (A[j*S+i+1] + A[j*S+i-1] + A[(j+1)*S+i] + A[(j-1)*S+i] – 4*prev;
B[j*S+i] = A[j*S+i] + dt * dT_dt; // use explicit integration
diff += fabs(B[j*S+i] - prev); // sum the absolute change
}
}
// delete the temporary array and return
if(diff / (S*S) < epsilon) { delete B; return; }
}
}dep
dep
Implementation 1 (write to a temp array)
Each grid point is independent within one time step
Requires 2X memory
Requires a larger cache (or more misses)
Memory and cache size are the first sacrifices in parallel processors
David Mayerich
STIM Laboratory, University of Houston
Implementation 2 (single array)
No additional memory
Better cache coherence
Both cases have a dependency in the terminating condition
// ....
diff += fabs(B[j*S+i] - prev)
}
}
if(diff / (S*S) < epsilon) return;David Mayerich
STIM Laboratory, University of Houston
How can we evaluate \(\frac{\partial T}{\partial t}\) in parallel?
Red vs. Blue - will this work?
No, red and blue are dependent on each other
Calculate all red, then all blue, etc.
Interleaving
David Mayerich
STIM Laboratory, University of Houston
P2
P2
P2
P1
P1
P1
How can we evaluate \(\frac{\partial T}{\partial t}\) in parallel?
Red vs. Blue - will this work?
No, red and blue are dependent on each other
Calculate all red, then all blue, etc.
Interleaving
Blocking
Less communication required
Distributed systems (send blocks to different computers)
Depends on architecture and problem (computation vs. memory access)
David Mayerich
STIM Laboratory, University of Houston
P2
P1
David Mayerich
STIM Laboratory, University of Houston
void heat_r(float* A, float alpha, float h, float dt, int S, float eps, int t_min, int t_max) {
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
diff = 0.0f; // track how much the simulation result has changed
// iterate through each pixel (why y then x?), note boundaries
for(int j=t_min; j<t_max; j++) { // iterate through lines associated with this thread
for(int i=1; i<S; i++) {
// calculate the partial derivative with respect to time (main calculation)
float prev = A[j*S+i];
dT_dt = c * (A[j*S+i+1] + A[j*S+i-1] + A[(j+1)*S+i] + A[(j-1)*S+i] – 4*prev;
B[j*S+i] = A[j*S+i] + dt * dT_dt; // use explicit integration
diff += fabs(B[j*S+i] - prev); // sum the absolute change
}
}
// delete the temporary array and return
if(diff / (S*S) < eps) { delete B; return; }
}
}race
race
David Mayerich
STIM Laboratory, University of Houston
void heat_r(float* A, float alpha, float h, float dt, int S, float epsilon, int t_min, int t_max) {
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
float diff = 0.0f; // track how much the simulation result has changed
// iterate through each pixel (why y then x?), note boundaries
for(int j=t_min; j<t_max; j++) { // iterate through lines associated with this thread
for(int i=1; i<S; i++) {
// calculate the partial derivative with respect to time (main calculation)
float prev = A[j*S+i];
dT_dt = c * (A[j*S+i+1] + A[j*S+i-1] + A[(j+1)*S+i] + A[(j-1)*S+i] – 4*prev;
B[j*S+i] = A[j*S+i] + dt * dT_dt; // use explicit integration
atomicAdd(diff, fabs(B[j*S+i] - prev); // sum the absolute change
}
}
// delete the temporary array and return
if(diff / (S*S) < epsilon) { delete B; return; }
}
}lock
David Mayerich
STIM Laboratory, University of Houston
void heat_r(float* A, float alpha, float h, float dt, int S, float epsilon, int t_min, int t_max) {
float c = alpha / (h * h); // pre-compute the spatial diffusion term
while(true) { // loop indefinitely
float thread_diff = 0.0f; // track how much the simulation result has changed
// iterate through each pixel (why y then x?), note boundaries
for(int j=t_min; j<t_max; j++) { // iterate through lines associated with this thread
for(int i=1; i<S; i++) {
// calculate the partial derivative with respect to time (main calculation)
float prev = A[j*S+i];
dT_dt = c * (A[j*S+i+1] + A[j*S+i-1] + A[(j+1)*S+i] + A[(j-1)*S+i] – 4*prev;
B[j*S+i] = A[j*S+i] + dt * dT_dt; // use explicit integration
thread_diff += fabs(B[j*S+i] - prev; // sum the absolute change
}
}
atomicAdd(diff, thread_diff);
// delete the temporary array and return
if(diff / (S*S) < epsilon) { delete B; return; }
}
}lock
David Mayerich
STIM Laboratory, University of Houston
Synchronization limits parallelism
time
occupancy
time
occupancy
Summing per-thread (thread_diff) with atomic operations (atomicAdd):
time
occupancy
sequential (\(P=1\))
parallel
adding within threads
What happens when the number of processors/threads approaches the number of grid points (\(P\rightarrow S^2\))?
t0
t1
tP
Divide Grid Between
Processors
synchronize
t0
t1
tP
synchronize
t0
t1
tP
diff
thread_diff
diff
Your ability to parallelize a program is limited by the serial parts:
\(S\) is the speedup
\(p\) is the parallel portion of the program
\(n\) is the number of processors
What is your expected speedup with 64 processors if 99.9% of your program can be parallelized?
David Mayerich
STIM Laboratory, University of Houston
Alternative approach gives more optimistic results:
\(S=1-p+pn\)
\(S\) is the speedup
\(p\) is the parallel portion of the code
\(n\) is the number of processors
What is your expected speedup with 64 processors if 99.9% of your program can be parallelized?
\(0.001 + 0.999 \times 64 \approx 64X\)
David Mayerich
STIM Laboratory, University of Houston
Why different answers (60X vs. 64X)?
Ahmdal's law represents a fixed problem size
Gustafson's law assumes the parallel part will grow to fully occupy the available processors
How would this apply to our heat equation example?