在求解许多大型线性方程组时应该使用什么映射策略?

计算科学 线性代数 并行计算 映射策略
2021-12-16 12:35:18

我正在解决一个问题,该问题涉及解决许多(数千个)不同的线性方程组,每个方程组都有数千个变量。让我们假设每个矩阵的大小完全相同,并且具有相同的结构(即相同的对称性、稀疏性、对角优势等)。

假设每个线性系统都用相同的方法求解,并且每个线性系统都是独立的,我预见到解决所有线性系统的三种方法:

  1. 我可以使用我可以使用的所有处理器解决每个线性系统,一次一个。

  2. 我可以为每个处理器分配一定数量的线性系统,然后依次求解每个。

  3. 前两个选项的混合......也就是说,如果我们有 P 个处理器,我们可以指定 G 个处理器组(为简单起见,假设 P 是 G 的倍数)。然后,对于 N 个线性方程组,我们将 N/G 个系统分配给每个组(为简单起见,假设 N 是 G 的倍数)。然后每个小组可以同时使用其 P/G 处理器一个接一个地并行解决其 N/G 系统。

我相信在最快的时间内产生所有方程组的解的映射策略取决于每个系统的大小和系统的数量。哪种策略效果最好:

  1. 如果系统的数量保持不变,但每个矩阵系统的大小会增加?

  2. 如果每个矩阵系统的大小保持不变,但系统的数量会增加?

2个回答

节点之间的通信(通常)比访问该节点本地的 RAM 慢得多。策略 2 可能是您最好的选择,除非您的系统太大以至于无法再放入一个节点上的内存中。

因为节点之间的通信通常比与硬盘的通信要快,所以你应该在多个节点之间分割你的问题(策略 3),如果它不适合单个处理器,则将整个事情保留在 RAM 中。如果每个问题都非常大,您需要所有可用的计算能力才能将其放入内存中,那么使用策略 1。

要回答您的问题:

  1. 如果您要处理的矩阵大到足以填满一个节点上的所有内存(您需要超过“数千个变量”才能做到这一点),请使用策略 1 或 3,具体取决于您的系统有多大是。如果它们不足以填满内存,请使用策略 2。

  2. 如果矩阵都可以放入主内存,那么不管你有多少个系统,都使用策略 2。

我会选择具有混合共享/分布式内存扭曲的选项二......我假设集群中的 13 台机器都访问相同的共享文件系统,并且每台计算机都有足够的内存来加载 8 个问题。

您的代码可能看起来像这样:

int fd, next_task = 0, my_task, nr_tasks;

/* Open the file you want to use to synchronize the nodes. */
if ( ( fd = open( "sharedlockfile" , O_CREAT ) ) < 0 ) {
    printf( "failed to create lock file.\n" );
    abort();
    }

/* Initialize the curr_task in the file. */
if ( <I am the first node in this cluster!> )
    if ( write( fd , &next_task , sizeof(int) ) < 0 ) {
        printf( "failed to write to lock file." );
        abort();
        }

/* Main parallel loop. */
#pragma omp parallel shared(fd,next_task), private(mytask)
while ( next_task < nr_tasks ) {

    /* Try to get a hold of the lock file, grab a task, and write the new counter. */
    #pragma omp critical
    { 

        /* Get a hold of the file lock (this will block until the file is free). */
        if ( flock( fd , LOCK_EX ) < 0 ) {
            printf( "file locking failed.\n" );
            abort(); // not sure I'm allowed to abort in a parallel block...
            }

        /* Read the index of the next task. */
        if ( lseek( fd , 0 , SEEK_SET ) < 0 || read( fd , &next_task , sizeof(int) ) < 0 ) {
            printf( "error reading next task id.\n" );
            abort();
            }

        /* Remember my task and update the counter. */
        my_task = next_task;
        next_task += 1;

        /* Write the index of the next task back to the file. */
        if ( lseek( fd , 0 , SEEK_SET ) < 0 || write( fd , &next_task , sizeof(int) ) < 0 ) {
            printf( "error writing next task id.\n" );
            abort();
            }

        /* Let go of the file. */
        if ( flock( fd , LOCK_UN ) < 0 ) {
            printf( "file un-locking failed.\n" );
            abort();
            }

        } /* end of critical section */

    /* Did we get a valid task ID? */
    if ( my_task < nr_tasks ) {

        /*
         * Do whatever this task implies.
         */

        } /* check if valid task ID. */

    } /* Main loop. */

请注意,我没有测试过上面的代码!我以前做过类似的事情,其中​​大部分都在我的脑海中......查看手册页flock以确定。

如果集群中的所有节点都执行此操作,则它们每个都应该生成与它们拥有的内核一样多的线程(或与您在 OMP_NUM_THREADS 中指定的一样多),并且每个内核都将尝试获取一个任务 ID。访问权限next_task在两个级别上进行控制:

  • 这样,同一节点上的#pragma omp critical任何两个线程都不会尝试同时弄乱该next_task变量。
  • 通过独占文件锁使得没有两个节点同时读取/更新next_task存储在文件中的变量。

这应该为您的计算提供良好的、自适应的、动态的调度。

编辑

嗯,锁定可能不像我想象的那么容易,因为它似乎依赖于 fd,因此可能无法跨节点工作。不过,这里似乎有一个很好的解决方案

附录

如果在单独的内核上解决每个系统对您的内存总线来说压力太大,例如,您注意到它根本无法很好地扩展,您可能想尝试MAGMA,它类似于 LAPACK,但在多核架构上可以很好地扩展。