φ(.. )メモシテオコウ TCP FASTOPENあり・なしの場合の速度比較めも

TCP FASTOPENの有効/無効で実際にどんな感じになるのかをsendto(2)でチェックしてみた。そんなに厳密なテストはしてませんけど。
実験は1台のLinuxマシンで実行でこのような構成。

cpu Intel(R) Core(TM) i7-3770S CPU @ 3.10GHz
memory 32GiB
Kernel 3.10-rc3
glibc glibc-2.16-31.fc18.x86_64
ディストリビューション Fedora 18 x86_64
Ethernet controller Realtek Semiconductor Co., Ltd. RTL8111/8168 PCI Express Gigabit Ethernet controller (rev 06)


テスト方法は以下の通り

  1. クライアントは並列実行では無く、シーケンシャルにアクセスする形
  2. サーバはTCP FASTOPEN有効な状態
  3. TFOありの場合は、クライアントはconnect(2)を実施しないでsendto(2)を直接実行
  4. TFOなしの場合は、クライアントはconnect(2)を実行してからsendto(2)を実行

これで1000回アクセスした結果のグラフが↓です。
f:id:masami256:20130530002028p:plain

テストの回数が少ないうちは時間がかかっているけど、後半になるに従ってブレはなくなってきてます(これはTFO以外の要因が起因?)。
TFO無しの場合、0.00005msecあたりに落ち着き、TFOありの場合は0.00004msecあたりに落ち着いてます。やっぱり、平均してTFOありの場合の方が速いですね。

時間の計測はTFOありの場合はこの様に、sendto(2)の時間、

	clock_gettime(CLOCK_REALTIME, &start);
	ret = sendto(sock, buf, strlen(buf), MSG_FASTOPNE_VALUE, 
					(struct sockaddr *) &sin, slen);
	clock_gettime(CLOCK_REALTIME, &end);

TFO無しの場合はconnect+sendtoの時間にしています。TFO無しの場合、conenct(2)しないとサーバに接続できませんし。

	clock_gettime(CLOCK_REALTIME, &start);
	connect(sock, (struct sockaddr *) &sin, slen);
	ret = sendto(sock, buf, strlen(buf), 0, 
					(struct sockaddr *) &sin, slen);
	clock_gettime(CLOCK_REALTIME, &end);

テストに使ったサーバ、クライアントはこの前作ったものをちょっと改造した程度ですが一応使ったものを張り付けておくと・・・
サーバはこれです。

#!/usr/bin/env ruby

require "socket"

def start_server(port = 1111)

    sock = Socket.open(Socket::AF_INET, Socket::SOCK_STREAM, 0)

    # Defined in include/linux/tcp.h
    # #define TCP_FASTOPEN            23      /* Enable FastOpen on listeners */
    sock.setsockopt(Socket::SOL_TCP, 23, 5)

    sock.bind(Addrinfo.tcp("127.0.0.1", port))

    sock.listen(20)

    while true
        s = sock.accept
        req = s[0].recv(64, Socket::MSG_PEEK)
        s[0].printf "fuction is %s\n", req
        s[0].close
    end

    sock.close
end

if __FILE__ == $0 then
    start_server()   
end

クライアントはこちらです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <time.h>

#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 1111

// Basically MSG_XXX are, such as MSG_PEEK, defined in /usr/include/bits/socket.h
// but glibc-headers-2.16-31.fc18.x86_64 does not define it.
#define MSG_FASTOPNE_VALUE 0x20000000

static int tfo_sendto(int sock);
static int notfo_sendto(int sock);
typedef int (*send_data_func_t)(int sock);

static struct timespec start, end;

#define NUMBER_OF_TESTS 1000
static double results[NUMBER_OF_TESTS];

static void
error_exit(void)
{
	fprintf(stderr, "[-]Error: %s\n", strerror(errno));
	exit(-1);
}

#define set_sockaddr_in(sin) \
	do { \
		sin.sin_family = AF_INET; \
		sin.sin_addr.s_addr = htonl(INADDR_ANY); \
		sin.sin_port = htons(SERVER_PORT); \
	} while (0)

static inline double
get_msec(const struct timespec *st)
{
	return (double) st->tv_sec + (double) st->tv_nsec * 1e-9;
}

static int
tfo_sendto(int sock)
{
	char *buf = "TFO";
	struct sockaddr_in sin = { 0 };
	socklen_t slen = sizeof(sin);
	int ret = 0;

	printf("[-]Start %s\n", __FUNCTION__);
	
	set_sockaddr_in(sin);

	clock_gettime(CLOCK_REALTIME, &start);
	ret = sendto(sock, buf, strlen(buf), MSG_FASTOPNE_VALUE, 
					(struct sockaddr *) &sin, slen);
	clock_gettime(CLOCK_REALTIME, &end);

	return ret;
}

static int notfo_sendto(int sock)
{
	char *buf = "NOT";
	struct sockaddr_in sin = { 0 };
	socklen_t slen = sizeof(sin);
	int ret = 0;

	printf("[-]Start %s\n", __FUNCTION__);
	
	set_sockaddr_in(sin);

	clock_gettime(CLOCK_REALTIME, &start);
	connect(sock, (struct sockaddr *) &sin, slen);
	ret = sendto(sock, buf, strlen(buf), 0, 
					(struct sockaddr *) &sin, slen);
	clock_gettime(CLOCK_REALTIME, &end);

	return ret;
}

static void
tfo_test_start(int cnt, send_data_func_t func)
{
	int sock = 0;
	int ret = 0;
	char buf[32] = { 0 };

	printf("[-]Start %s\n", __FUNCTION__);
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == -1) 
		error_exit();

	ret = func(sock);
	if (ret < 0)
		error_exit();

	ret = recv(sock, buf, sizeof(buf), MSG_PEEK);
	if (ret == -1)
		error_exit();
	printf("[-] recv %s\n",  buf);

	results[cnt] = get_msec(&end) - get_msec(&start);

	shutdown(sock, SHUT_RDWR);
}

int
main(int argc, char **argv)
{
	send_data_func_t func = &tfo_sendto;
	int i = 0;
	char *test_type = "use TFO";

	if (argc == 2) {
		if (!strcmp("notfo", argv[1])) {
			func = &notfo_sendto;
			test_type = "not use TFO";
		}
	}

	for (i = 0; i < NUMBER_OF_TESTS; i++) {
	  	printf("[-]Run test %d\n", i);	
		tfo_test_start(i, func);
	}

	printf("Test result %s\n", test_type);

	for (i = 0; i < NUMBER_OF_TESTS; i++)
		printf("Test[%d] %5.5f msec\n", i, results[i]);

	return 0;
}

コンパイルはclock_gettime(2)を使っているので-lrtが必要です。

$ gcc tfo_test_client.c -lrt -o tfo_test_client -Wall