send til socket: FreeBSD vs. Linux
Jeg er blevet meget overrasket over at FreeBSD, som ellers lader til at køre hurtigere end Linux mht. alle andre ting, tilsyneladende er meget langsommere/dårligere end Linux når det drejer sig om at sende til en TCP socket.Jeg har lavet et lille benchmark program, som bare er en primitiv server, der sender så mange gange, den kan, når der etableres forbindelse (med telnet). Programmet viser hvor mange gange der bliver sendt før sendbufferen fyldes op og hvor lang tid det tager.
Programmet er kørt på PC med følgende hardware:
Bundkort: ECS K7S5A
RAM: 256 MB PC133
CPU: AMD Duron 900 MHz
NIC: RealTek 8139 baseret PCI Ethernet kort (fra Zonet)
FreeBSD installeret på Seagate Barracuda IV (40 GB 7200 RPM)
Linux installeret på en 20 GB 5400 RPM disk fra Seagate
Her er programmet:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/select.h>
#define TIME_DIFFERENCE(t2, t1) (int)((t2.tv_sec-t1.tv_sec)*1000000 + t2.tv_usec-t1.tv_usec)
void fail(const char* msg)
{
perror(msg);
exit(1);
}
void sigpipe_handler(int code)
{
printf("Sigpipe. Code: %d\n", code);
}
int main(void)
{
fd_set master, rfds;
struct sockaddr_in myaddr, remoteaddr;
struct timeval before, after;
int listener, sockfd, saveflags, yes=1, i, j, n;
char buf[256];
const long sndbufsize = 65536;
signal(SIGPIPE, sigpipe_handler);
if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1)
fail("socket");
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
fail("setsockopt");
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = INADDR_ANY;
myaddr.sin_port = htons(1027);
memset(&(myaddr.sin_zero), '\0', 8);
if (bind(listener, (struct sockaddr *)&myaddr, sizeof(struct sockaddr)) == -1)
fail("bind");
if (listen(listener, 10) == -1)
fail("listen");
FD_ZERO(&master);
FD_SET(listener, &master);
while (1)
{
rfds = master;
if (select(listener+1, &rfds, NULL, NULL, NULL) < 0)
fail("select()");
if (gettimeofday(&before, NULL) != 0)
fail ("gettimeofday()");
if (FD_ISSET(listener, &rfds))
{
if ((sockfd = accept(listener, NULL, 0)) == -1)
fail("accept");
saveflags = fcntl(sockfd,F_GETFL,0);
if(saveflags < 0)
fail("fcntl1");
if(fcntl(sockfd, F_SETFL, saveflags|O_NONBLOCK) < 0)
fail("fcntl2");
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (void*)&sndbufsize, (socklen_t)sizeof(int)) < 0)
fail("setsockopt()");
}
i = sprintf(buf, "Hejsa! Det er idag et vejr, et solskinsvejr... din socket har nummer %d\r\n", sockfd);
for (j=0; j<10000; j++)
{
//n = write(sockfd, buf, i);
n = send(sockfd, buf, i,0);
if (n < i)
{
perror("send()");
break;
}
}
if (gettimeofday(&after, NULL) != 0)
fail ("gettimeofday()");
printf("send() kaldt %d gange\nTid: %d usec\n", j, TIME_DIFFERENCE(after, before));
close(sockfd);
}
return 0;
}
(oversættes med gcc på begge OS)
Og her er resultater af testkørsel:
RedHat Linux 7.2 (Kerne 2.4.17):
send(): Success
send() kaldt 1501 gange
Tid: 3555 usec
send(): Illegal seek
send() kaldt 1501 gange
Tid: 4944 usec
send(): Illegal seek
send() kaldt 1501 gange
Tid: 3233 usec
send(): Illegal seek
send() kaldt 1501 gange
Tid: 3279 usec
send(): Illegal seek
send() kaldt 1501 gange
Tid: 3301 usec
(gennemsnit: 2.4 ms pr. 1000 linier)
FreeBSD 4.5
send(): Resource temporarily unavailable
send() kaldt 972 gange
Tid: 6854 usec
send(): Resource temporarily unavailable
send() kaldt 931 gange
Tid: 6145 usec
send(): Resource temporarily unavailable
send() kaldt 972 gange
Tid: 6973 usec
send(): Resource temporarily unavailable
send() kaldt 972 gange
Tid: 6929 usec
send(): Resource temporarily unavailable
send() kaldt 931 gange
Tid: 6482 usec
(gennemsnit: 6.8 ms pr. 1000 linier)
FreeBSD er altså knap 3 gange så lang tid om at udføre det samme.
Hvad skyldes det?
Kan man tune FreeBSD til samme performance som Linux? Hvordan?