【问题标题】:MPI - Printing in an orderMPI - 按订单打印
【发布时间】:2021-06-25 15:33:39
【问题描述】:

我正在尝试用 C 语言编写一个函数,每个处理器都打印它自己的数据。 这是我所拥有的:

void print_mesh(int p,int myid,int** U0,int X,int Y){
    int i,m,n;
    for(i=0;i<p;i++){
        if(myid==i){
            printf("myid=%d\n",myid);
            for(n=0;n<X;n++){
                for(m=0;m<Y;m++){
                    printf("%d ",U0[n][m]);
                }
                printf("\n");
            }
        }
        else MPI_Barrier(MPI_COMM_WORLD);
    }
}

由于某种原因它不起作用。阵列被打印出来混合在一起。 您对为什么这不起作用有任何见解吗?还有其他可行的想法吗? 如果可能的话,我不想在主进程中发送整个数组。我也不想使用预编译的函数。

【问题讨论】:

  • 韦斯利·布兰德是对的;由于缓冲,没有一般的方法可以做到这一点。我一直使用您在示例代码中使用的相同方法,输出很小,实际上它通常工作得很好,但不能保证,它肯定不适用于大量输出(>而不是单个I/O 缓冲区)。最好是使用 MPI-IO 写入文件(例如,this answer),同样需要注意的是,最好以二进制格式写入大量数据。
  • 还请注意,您在MPI_COMM_WORLD 中调用MPI_Barrier,并且在每一轮中,MPI_COMM_WORLD 中的一个等级无法调用它。对MPI_Barrier 的调用不应在条件构造的else 块中,即删除else 关键字。

标签: c parallel-processing printf mpi


【解决方案1】:

无法保证来自许多不同进程的消息在到达另一个进程时会以“正确”的顺序到达。这基本上就是这里发生的事情。

即使您没有明确发送消息,当您在屏幕上打印某些内容时,也必须将其发送到本地系统上的进程(mpiexecmpirun),在那里它可以打印到屏幕上. MPI 无法知道这些消息的正确顺序是什么,所以它只是在它们到达时打印它们。

如果您要求以特定顺序打印您的消息,您必须将它们全部发送到一个等级,该等级可以按照您喜欢的任何顺序打印它们。只要一个等级完成所有的打印,所有的消息都会被正确排序。

应该说,你可能会在那里找到答案,说你可以在字符串的末尾放置一个换行符或使用 flush() 来确保缓冲区被刷新,但这不会由于上述原因,保证在远程端订购。

【讨论】:

  • +1; fflush(stdout) 当然值得一试,但是是的,不能保证,在一个系统上有效的东西可能在另一个系统上无效。
【解决方案2】:

所以,你可以这样做:

MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank == 0) {
    MPI_Send(&message, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
    printf("1 SIZE = %d RANK = %d MESSAGE = %d \n",size,rank, message);
} else {
    int buffer;
    MPI_Status status;
    MPI_Probe(MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &status);
    MPI_Get_count(&status, MPI_INT, &buffer);
    if (buffer == 1) {
        printf("2 SIZE = %d RANK = %d MESSAGE = %d \n",size,rank, message);
        MPI_Recv(&message, buffer, MPI_INT, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, &status);
        if (rank + 1 != size) {
            MPI_Send(&message, 1, MPI_INT, ++rank, 0, MPI_COMM_WORLD);
        }
    };
};
MPI_Finalize();

执行后:

$ mpirun -n 5 ./a.out 
1 SIZE = 5 RANK = 0 MESSAGE = 999 
2 SIZE = 5 RANK = 1 MESSAGE = 999 
2 SIZE = 5 RANK = 2 MESSAGE = 999 
2 SIZE = 5 RANK = 3 MESSAGE = 999 
2 SIZE = 5 RANK = 4 MESSAGE = 999 

【讨论】:

  • 你在哪里分配“消息”?
【解决方案3】:

Святослав Павленко 的回答启发了我:使用阻塞 MPI 通信来强制实时串行输出。虽然 Wesley Bland 认为 MPI 不是为串行输出而构建的。因此,如果我们想要输出数据,那么让每个处理器输出(非冲突)数据都是有意义的。或者,如果数据的顺序很重要(并且不太大),推荐的方法是将其全部发送到 cpu(例如 0 级),然后正确格式化数据。

对我来说,这似乎有点矫枉过正,尤其是当数据可以是可变长度字符串时,std::cout &lt;&lt; "a=" &lt;&lt; some_varible &lt;&lt; " b=" &lt;&lt; some_other_variable 通常就是这样。因此,如果我们想要一些快速而肮脏的有序打印,我们可以利用 Святослав Павленко 的答案来构建串行输出流。此解决方案运行良好,它的性能与许多 cpu 的扩展性很差,所以不要将它用于数据输出!

#include <iostream>
#include <sstream>
#include <mpi.h>

MPI 内务管理:

int mpi_size;
int mpi_rank;

void init_mpi(int argc, char * argv[]) {
    MPI_Init(& argc, & argv);
    MPI_Comm_size(MPI_COMM_WORLD, & mpi_size);
    MPI_Comm_rank(MPI_COMM_WORLD, & mpi_rank);
}

void finalize_mpi() {
    MPI_Finalize();
}

启用 MPI 消息链的通用类

template<class T, MPI_Datatype MPI_T> class MPIChain{
    // Uses a chained MPI message (T) to coordinate serial execution of code (the content of the message is irrelevant).
    private:
        T message_out; // The messages aren't really used here
        T message_in;
        int size;
        int rank;

    public:
        void next(){
            // Send message to next core (if there is one)
            if(rank + 1 < size) {
            // MPI_Send - Performs a standard-mode blocking send.
            MPI_Send(& message_out, 1, MPI_T, rank + 1, 0, MPI_COMM_WORLD);
            }
        }

        void wait(int & msg_count) {
            // Waits for message to arrive. Message is well-formed if msg_count = 1
            MPI_Status status;

            // MPI_Probe - Blocking test for a message.
            MPI_Probe(MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, & status);
            // MPI_Get_count - Gets the number of top level elements.
            MPI_Get_count(& status, MPI_T, & msg_count);

            if(msg_count == 1) {
                // MPI_Recv - Performs a standard-mode blocking receive.
                MPI_Recv(& message_in, msg_count, MPI_T, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, & status);
            }
        }

        MPIChain(T message_init, int c_rank, int c_size): message_out(message_init), size(c_size), rank(c_rank) {}

        int get_rank() const { return rank;}
        int get_size() const { return size;}
};

我们现在可以使用我们的MPIChain 类来创建管理输出流的类:

class ChainStream : public MPIChain<int, MPI_INT> {
    // Uses the MPIChain class to implement a ostream with a serial operator<< implementation.
    private:
        std::ostream & s_out;

    public:
        ChainStream(std::ostream & os, int c_rank, int c_size)
            : MPIChain<int, MPI_INT>(0, c_rank, c_size), s_out(os) {};

        ChainStream & operator<<(const std::string & os){
            if(this->get_rank() == 0) {
                this->s_out << os;
                // Initiate chain of MPI messages
                this->next();
            } else {
                int msg_count;
                // Wait untill a message arrives (MPIChain::wait uses a blocking test)
                this->wait(msg_count);
                if(msg_count == 1) {
                    // If the message is well-formed (i.e. only one message is recieved): output string
                    this->s_out << os;
                    // Pass onto the next member of the chain (if there is one)
                    this->next();
                }
            }

            // Ensure that the chain is resolved before returning the stream
            MPI_Barrier(MPI_COMM_WORLD);

            // Don't output the ostream! That would break the serial-in-time exuction.
            return *this;
       };
};

注意operator&lt;&lt; 末尾的MPI_Barrier。这是为了防止代码启动第二个输出链。尽管这可以移到operator&lt;&lt; 之外,但我想我会把它放在这里,因为无论如何这应该是串行输出......

把它们放在一起:

int main(int argc, char * argv[]) {
    init_mpi(argc, argv);

    ChainStream cs(std::cout, mpi_rank, mpi_size);

    std::stringstream str_1, str_2, str_3;
    str_1 << "FIRST:  " << "MPI_SIZE = " << mpi_size << " RANK = " << mpi_rank << std::endl;
    str_2 << "SECOND: " << "MPI_SIZE = " << mpi_size << " RANK = " << mpi_rank << std::endl;
    str_3 << "THIRD:  " << "MPI_SIZE = " << mpi_size << " RANK = " << mpi_rank << std::endl;

    cs << str_1.str() << str_2.str() << str_3.str();
    // Equivalent to:
    //cs << str_1.str();
    //cs << str_2.str();
    //cs << str_3.str();

    finalize_mpi();
}

请注意,我们将字符串 str_1str_2str_3 连接起来我们向它们发送 ChainStream 实例之前。通常人们会做这样的事情:

std::cout << "a" << "b" << "c"" << std::endl

但这适用于从左到右的operator&lt;&lt;,我们希望字符串准备好输出之前依次运行每个进程。

g++-7 -O3 -lmpi serial_io_obj.cpp -o serial_io_obj
mpirun -n 10 ./serial_io_obj

输出:

FIRST:  MPI_SIZE = 10 RANK = 0
FIRST:  MPI_SIZE = 10 RANK = 1
FIRST:  MPI_SIZE = 10 RANK = 2
FIRST:  MPI_SIZE = 10 RANK = 3
FIRST:  MPI_SIZE = 10 RANK = 4
FIRST:  MPI_SIZE = 10 RANK = 5
FIRST:  MPI_SIZE = 10 RANK = 6
FIRST:  MPI_SIZE = 10 RANK = 7
FIRST:  MPI_SIZE = 10 RANK = 8
FIRST:  MPI_SIZE = 10 RANK = 9
SECOND: MPI_SIZE = 10 RANK = 0
SECOND: MPI_SIZE = 10 RANK = 1
SECOND: MPI_SIZE = 10 RANK = 2
SECOND: MPI_SIZE = 10 RANK = 3
SECOND: MPI_SIZE = 10 RANK = 4
SECOND: MPI_SIZE = 10 RANK = 5
SECOND: MPI_SIZE = 10 RANK = 6
SECOND: MPI_SIZE = 10 RANK = 7
SECOND: MPI_SIZE = 10 RANK = 8
SECOND: MPI_SIZE = 10 RANK = 9
THIRD:  MPI_SIZE = 10 RANK = 0
THIRD:  MPI_SIZE = 10 RANK = 1
THIRD:  MPI_SIZE = 10 RANK = 2
THIRD:  MPI_SIZE = 10 RANK = 3
THIRD:  MPI_SIZE = 10 RANK = 4
THIRD:  MPI_SIZE = 10 RANK = 5
THIRD:  MPI_SIZE = 10 RANK = 6
THIRD:  MPI_SIZE = 10 RANK = 7
THIRD:  MPI_SIZE = 10 RANK = 8
THIRD:  MPI_SIZE = 10 RANK = 9

【讨论】:

    【解决方案4】:

    MPI 标准未指定应如何收集来自不同节点的标准输出,fflush 也无济于事。

    如果您需要按顺序打印大量输出,最好的解决方案可能不是将它们全部收集并立即打印,因为这会在网络上产生流量。 更好的解决方案是创建类似于虚拟环的东西,其中每个进程等待来自前一个进程的令牌,打印并将令牌发送给下一个进程。当然第一个进程不必等待,它会打印并发送到下一个进程。

    无论如何,如果输出非常大,在视频上打印输出可能没有意义,您应该按照 Jonathan Dursi 的建议使用 MPI-IO。

    【讨论】:

      【解决方案5】:

      出于调试和开发目的,您可以在单独的终端中运行每个进程,以便它们在自己的终端中打印:

      mpirun -np n xterm -hold -e ./output
      

      n:处理器数量
      -hold:在程序完成后保持 xterm 开启。
      output:MPI 可执行文件的名称

      【讨论】:

        【解决方案6】:

        在 C++ 中,我在给定等级中使用过一次打印,它会打印有序的脱节显示

        cout<<"The capabilities of Node "<<node_name<<" are: \n";
        
        cout<<"no of real cores = "<<rcores<< " \n";
        cout<<"no of virtual cores = "<<vcores<<" \n";
        cout<<"clock speed of Processor = "<<speed<<" MHz \n";
        cout<<"RAM size is "<<ramsize<<"\n"<<endl;
        

        输出是screenshot of output

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-07-01
          • 1970-01-01
          • 2013-03-31
          • 2013-11-10
          • 1970-01-01
          • 2021-09-24
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多