测试Fortran IPC:使用TCP/IP发送4 MiB的数据和发送100 B一样快

3
我目前正在测试不同的方法来高效地在两个Fortran程序之间传递数据。我已经尝试过写入文件、管道和TCP/IP。下面是我的测试结果。请注意,图表仅显示4*10^4 B的时间,因为这是我为文件和管道所测试的最大值。4*10^6 B的测试仅针对TCP/IP程序进行。
为了测试执行时间,我使用了大多数Linux发行版中都有的time程序,例如:time ./program。我使用real值作为我的时间。 Graph, showing the time it took to send, process, and send back data. 很明显,写入文件和使用管道是相当线性的。虽然有一些开销,但它非常直接。然而,TCP/IP协议似乎不受影响 - 无论数据量大小如何。
实际上正在发生的是:
- B.f90启动并调用server.c,在localhost:55555上启动服务器 - A.f90启动并调用client.c,连接到服务器 - A.f90通过客户端/服务器将N个整数(每个4字节)传递给B.f90 - B.f90平方每个整数并将它们发送回A.f90
下面是执行此操作的代码,共有4个程序。令我困惑的是,TCP/IP版本的程序似乎不受数据量大小的影响。我在发布此问题之前尝试发送了10^6个整数(4 MiB),速度非常快。但是,发送10^7个整数会导致程序崩溃(段错误)。
A.f90
program performance_test
use iso_c_binding, only: C_CHAR, C_NULL_CHAR, C_INT, C_PTR, C_LOC
implicit none

    ! Interfaces that ensure type compatability between Fortran and C.
    interface
        subroutine client(ipaddr, portnum) bind(C, name="client")
            use iso_c_binding, only: c_char, c_int
            character(kind=c_char) :: ipaddr(*)
            integer(kind=c_int), value :: portnum
        end subroutine client

        subroutine calc(indata, length) bind(C, name="calc")
            use iso_c_binding, only: c_ptr, c_int
            implicit none
            integer(c_int), value :: length
            type(c_ptr), value :: indata
        end subroutine calc

    end interface

    ! type declaration statements
    integer(c_int), allocatable, target :: array(:)
    type(c_ptr) :: cptr
    integer portno, length, i

    ! executable statements
    ! Call client.c and connect to localhost on port number `portno'.
    portno=55555
    call client(C_CHAR_"localhost"//C_NULL_CHAR, portno)

    ! Put numbers in the array
    length = 1000000
    allocate(array(0:length))
    cptr=c_loc(array(1))

    do i=1, length
        array(i) = 2
    end do

    ! Call client.c and pass the query on towards calcs.f90.
    call calc(cptr, length)

    deallocate(array)

end program performance_test

client.c

/* The original code for this client can be found here:
 * http://www.cs.rpi.edu/~moorthy/Courses/os98/Pgms/client.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 

// Static variables
static int sockfd;


/* This function is called when a system call fails. It displays a message about
 * the error on stderr and then aborts the program. */
void error(char *msg)
{
    perror(msg);
    exit(0);
}

/* Callable function from Fortran, used by calcf.f90, to connect to a server.
 */
int client(char *ipaddr, int in_portno)
{
    int portno;

    struct sockaddr_in serv_addr;
    struct hostent *server;

    portno = in_portno;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) 
        error("ERROR opening socket");

    server = gethostbyname(ipaddr);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }

    memset(((char *) &serv_addr), 0, (sizeof(serv_addr)));
    serv_addr.sin_family = AF_INET;
    memcpy(((char *)server->h_addr),
           ((char *)&serv_addr.sin_addr.s_addr),
           (server->h_length));
    serv_addr.sin_port = htons(portno);

    if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) 
        error("ERROR connecting");

    return 0;
}

/* Callable function from Fortran, used by calcf.f90, as a calculator.
 * calc passes the query stored in buffer to server and returns the
 * answer. */
int *calc(int *indata, int length)
{

    int n;
    n = write(sockfd, indata, sizeof(int)*length);
    if (n < 0) 
         error("ERROR writing to socket");

    n = read(sockfd, indata, sizeof(int)*length);
    if (n < 0) 
        error("ERROR reading from socket");

    return indata;
}

server.c

/* The original code for this server can be found here:
 * http://www.cs.rpi.edu/~moorthy/Courses/os98/Pgms/server.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

/* This function is called when a system call fails. It displays a message about
 * the error on stderr and then aborts the program. */
void error(char *msg)
{
    perror(msg);
    exit(1);
}

/* Callable function from Fortran, used by calcs.f90, to start a server that
 * can recieve queries. */
int server(int in_portno)
{
   int sockfd, newsockfd, portno, clilen;

    struct sockaddr_in serv_addr, cli_addr; int n;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
       error("ERROR opening socket");

    memset(((char *) &serv_addr), 0, (sizeof(serv_addr)));

    portno = in_portno;

    serv_addr.sin_family = AF_INET;

    serv_addr.sin_addr.s_addr = INADDR_ANY;

    serv_addr.sin_port = htons(portno);

    if (bind(sockfd, (struct sockaddr *) &serv_addr,
             sizeof(serv_addr)) < 0)
             error("ERROR on binding");

    listen(sockfd,5);

    clilen = sizeof(cli_addr);

    while(1)
    {
        int length = 1000000;
        int indata[length];
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
        if (newsockfd < 0)
             error("ERROR on accept");

        // Here comes the query from client.c
        n = read(newsockfd, indata, sizeof(int)*length);
        if (n < 0) error("ERROR reading from socket");

        square(indata);

        // write returns the data to the client
        n = write(newsockfd, indata, sizeof(int)*length);

        if (n < 0) error("ERROR writing to socket");
    }

    return 0;

}

B.f90

program calculator_server

! Calculator at the server side of the client/server calculator
! Responsible for starting up the server server.c

use iso_c_binding, only: C_CHAR, C_NULL_CHAR, C_INT, C_PTR
implicit none

    ! type declaration statements
    integer calc, ans, portnum, calculate

    ! Interface that ensures type compatibility between Fortran and C
    interface
        subroutine server(portnum) bind(C, name="server")
            use iso_c_binding, only: c_int
            integer(kind=c_int), value :: portnum
        end subroutine server
        subroutine square(intarray) bind(C, name="square")
            use iso_c_binding
            type(c_ptr), value :: intarray
        end subroutine square
    end interface

    ! Start the server with portnumber
    portnum = 55555
    call server(portnum)

end program calculator_server


! **********************************************************************
subroutine square(cptr) bind(C, name="square")
    use iso_c_binding
    implicit none

    ! Variable declarations
    type(c_ptr) :: cptr
    integer*8 :: iptr
    integer :: length, i
    integer pointee(1000000)
    pointer(iptr, pointee)
    iptr = loc(cptr)
    length = 1000000

    ! Execution
    do i = 1, length
        pointee(i)= pointee(i)**2
    end do
end subroutine square

我的问题只是想知道我是否遗漏了什么。当然,我已经在程序的各个阶段打印了数据以确保它实际上被传递、平方和发送回来,所以程序可以按照预期执行。但是,我无法理解数据量不重要的情况。目前我无法在两台不同的机器上尝试该程序,否则我也会这样做。

非常感谢您对可能导致此行为的任何想法。


你尝试过增加发送的整数数量吗(比如到十个或者甚至一亿)?你如何测量时间? - Some programmer dude
这正是图表所显示的。我开始发送25个整数(25 * 4 = 100 B的数据),然后根据图表逐渐增加。当我达到1000万个整数(即40 * 10 ^ 6 = 40 MiB的数据)时,我遇到了一个段错误,但我从未深入研究过原因。无论如何,我最多只会每次发送不超过10 ^ 4 B的数据,但我想看看会发生什么。使用“time”命令在任意Linux发行版中测量时间。这个特定的版本是OpenSuse 13.1(Bottle)。 - GLaDER
从我所看到的图表中,您提供的性能显示了发送高达4*10^4字节消息时的表现。在我们都拿出测试盒和水晶球之前,请向我们保证您没有犯基本错误。请告诉我们您如何计时操作。 - High Performance Mark
@HighPerformanceMark,我编辑了我的帖子以增加清晰度。 410^6 B测试仅使用TCP/IP程序运行,因此未在图表中显示。但是,如图所示,对于410^4 B运行,这个问题也同样有趣。 - GLaDER
你所说的以太网是什么?如果它是10GE,那么最长消息的最短时间为4000000/10000000000=0.4毫秒,因此你只会看到程序中的开销。我建议多次发送消息并取平均值。不过,实际上我会使用MPI,但这不是你问的问题。 - Ian Bush
@IanBush,我没有离开电脑,正如代码所示,我通过localhost传递数据。我已经开始研究MPI,但并不真正看到它能以我想要使用程序的方式工作。 - GLaDER
1个回答

1

至少存在一个问题: read() 可能会返回比请求的数据少。同样,send() 可能无法发送所有数据。在以下方式中向 server.c 插入一些 printf()

// Here comes the query from client.c
n = read(newsockfd, indata, sizeof(int)*length);
if (n < 0) error("ERROR reading from socket");

printf("received %d, expected %lu\n", n, sizeof(int)*length);
square(indata);

// write returns the data to the client
n = write(newsockfd, indata, sizeof(int)*length);
printf("sent %d out of %lu\n", n, sizeof(int)*length);

执行时打印出:

收到 43690,期望值为 4000000

发送了 2539008,总共需要发送 4000000

因此只收到了预期数据的 1/90。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接