【发布时间】:2019-04-28 15:25:55
【问题描述】:
总结:你如何避免不同线程的不同工作负载导致的性能损失? (每个线程都有一个while循环的内核)
问题: 我想在许多不同的初始条件下使用 Runge-Kutta 求解粒子轨迹(由二阶微分方程描述)。轨迹通常具有不同的长度(每个轨迹在粒子撞击某个目标时结束)。此外,为了确保数值稳定性,Runge-Kutta 步长是自适应设置的。这会导致两个嵌套的 while 循环,迭代次数未知(请参见下面的系列示例)。
我想实现 Runge-Kutta 例程以在具有 CUDA/C++ 的 GPU 上运行。轨迹彼此之间没有依赖关系,因此作为第一种方法,我将在不同的初始条件上并行化,这样每个线程将对应一个唯一的轨迹。当一个线程完成粒子轨迹后,我希望它从一个新的开始。
但是,如果我理解正确的话,每个 while 循环(粒子轨迹)的未知长度意味着不同的线程将获得不同的工作量,这可能会导致 GPU 上的严重性能损失.
问题:这是否可能(以简单的方式)克服不同线程的不同工作负载导致的性能损失?例如将每个经线大小设置为只有 1,这样每个线程(经线)就可以独立运行? r 这是否会导致其他性能损失(例如,没有合并的内存读取)?
串口伪代码:
// Solve a particle trajectory for each inital condition
// N_trajectories: much larger than 1e6
for( int t_i = 0; t_i < N_trajectories; ++t_i )
{
// Set start coordinates
double x = x_init[p_i];
double y = y_init[p_i];
double vx = vx_init[p_i];
double vy = vy_init[p_i];
double stepsize = ...;
double tolerance = ...;
...
// Solve Runge-Kutta trajectory until convergence
int converged = 0;
while ( !converged )
{
// Do a Runge-Kutta step, if step-size too large then decrease it
int goodStepSize = 0
while( !goodStepSize )
{
// Update x, y, vx, vy
double error = doRungeKutta(x, y, vx, vy, stepsize);
if( error < tolerance )
goodStepSize = 1;
else
stepsize *= 0.5;
}
if( (abs(x-x_final) < epsilon) && (abs(y-y_final) < epsilon) )
converged = 1;
}
}
对我的代码进行的简短测试表明,在找到令人满意的 Runge-Kutta 步长之前,内部 while 循环在 99% 的所有情况下运行 2-4 次,在 1% 的所有情况下运行 >10 次。
并行伪代码:
int tpb = 64;
int bpg = (N_trajectories + tpb-1) / tpb;
RungeKuttaKernel<<<bpg, tpb>>>( ... );
__global__ void RungeKuttaKernel( ... )
{
int idx = ...;
// Set start coordinates
double x = x_init[idx];
...
while ( !converged )
{
...
while( !goodStepSize )
{
double error = doRungeKutta( ... );
...
}
...
}
}
【问题讨论】:
-
将扭曲大小设置为 1 是个坏主意。它会使您将 GPU 性能降低 97%。相反,如果可以的话,重新安排线程之间的工作,使它们在每个经线中大致相同。如果您可以预测每个线程的工作量,这可以使用某种排序方法来完成。
-
在内核中使用无限循环计数是个坏主意。使用涵盖大多数情况的固定计数来收敛。如果一个线程检测到它没有收敛,让它存储它的解决方案并设置一个标志,然后只对有标志的情况启动第二轮计算。重复直到一切都收敛。这将更加优化
-
@RobertCrovella 不幸的是,无法预测工作量。我很想知道为什么每个经线只有一个线程会严重降低性能?
-
@talonmies 这是个好主意,我之前也使用过类似的方法。这种情况下的问题是还有一个内部while循环。我可以通过设置一个对于所有情况都足够小的恒定步长来消除这个内部循环,但是在 Runge-Kutta 中具有自适应步长会显着加快它(通过几个因素)。两个 while 循环确实是一个非常连续的问题,但我必须解决这个问题非常多次,以至于(应该)在 GPU 上进行并行化是有保证的。
标签: c++ parallel-processing cuda gpu runge-kutta