Skip to contentSkip to conversation

TCP Zero Copy for mmapped files

PATCH In review Thomas Ogrisegg 6 participants 19 messages Dec 30, 2002 – Jan 6, 2003

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 TCP Zero Copy for mmapped files

The following patch (for 2.4.20 -- should work with all kernels above 2.4.17) implements TCP Zero Copy for normal (writing) socket operations on memory mapped files.

This is a major speedup for the TCP/IP stack (depending on the size of the file more than 100% more throughput) and makes sendfile(2) nearly useless.

BTW: When I did a (loopback) benchmark against my very own HTTP- Server it outperformed TUX by roughly 6%. With logging disabled by roughly 20%.

Please CC any replies to me, as I'm not subscribed to this list.

net/ipv4/tcp.c +75 -0
@@ -204,6 +204,7 @@
  *		Andi Kleen 	:	Make poll agree with SIGIO
  *	Salvatore Sanfilippo	:	Support SO_LINGER with linger == 1 and
  *					lingertime == 0 (RFC 793 ABORT Call)
+ *	Thomas Ogrisegg		:	Added TCP Zero Copy for mmapped files
  *					
  *		This program is free software; you can redistribute it and/or
  *		modify it under the terms of the GNU General Public License
@@ -1006,6 +1007,41 @@
 	return tmp;
 }
 
+static ssize_t file_send_actor (read_descriptor_t *desc, struct page *page,
+	unsigned long offset, unsigned long size)
+{
+	ssize_t res;
+	unsigned long count = desc->count;
+	struct sock *sk = (struct sock *) desc->buf;
+	int flags;
+
+	if (size > count)
+		size = count;
+
+	flags = (sk->socket->file->f_flags & O_NONBLOCK) ? MSG_DONTWAIT : 0;
+	if (size < count) flags |= MSG_MORE;
+
+#define TCP_ZC_CSUM_FLAGS (NETIF_F_IP_CSUM|NETIF_F_NO_CSUM|NETIF_F_HW_CSUM)
+
+	if (!(sk->route_caps & NETIF_F_SG) ||
+		!(sk->route_caps & TCP_ZC_CSUM_FLAGS))
+		return sock_no_sendpage(sk->socket, page, offset, size, flags);
+
+#undef TCP_ZC_CSUM_FLAGS
+
+	TCP_CHECK_TIMER(sk);
+	res = do_tcp_sendpages(sk, &page, offset, size, flags);
+	TCP_CHECK_TIMER(sk);
+
+	if (res < 0) desc->error = res;
+	else {
+		desc->count -= res;
+		desc->written += res;
+	}
+
+	return res;
+}
+
 int tcp_sendmsg(struct sock *sk, struct msghdr *msg, int size)
 {
 	struct iovec *iov;
@@ -1015,6 +1051,7 @@
 	int mss_now;
 	int err, copied;
 	long timeo;
+	int has_sendpage = sk->socket->file->f_op->sendpage != NULL;
 
 	tp = &(sk->tp_pinfo.af_tcp);
 
@@ -1049,6 +1086,44 @@
 
 		iov++;
 
+		if (seglen >= PAGE_SIZE && has_sendpage) {
+			struct vm_area_struct *vma =
+				find_vma (current->mm, (long) from);
+			struct file *filp;
+
+			if (vma && (filp = vma->vm_file)) {
+				read_descriptor_t desc;
+				struct inode *in, *out;
+				loff_t pos = (long) from - vma->vm_start;
+
+				in  = filp->f_dentry->d_inode;
+				out = sk->socket->file->f_dentry->d_inode;
+
+				if (locks_verify_area (FLOCK_VERIFY_READ, in,
+					filp, filp->f_pos, seglen))
+					goto out_no_zero_copy;
+
+				if (locks_verify_area (FLOCK_VERIFY_WRITE, out,
+					sk->socket->file, 0, seglen))
+					goto out_no_zero_copy;
+
+				desc.written = 0;
+				desc.count   = seglen;
+				desc.buf     = (char *) sk;
+				desc.error   = 0;
+
+				do_generic_file_read (filp, &pos, &desc,
+					file_send_actor);
+
+				if (!desc.written) {
+					err = desc.error;
+					goto do_error;
+				}
+				copied += desc.written;
+				continue;
+			}
+		}
+out_no_zero_copy:
 		while (seglen > 0) {
 			int copy;
 			
#1 lore raw

How about putting this into a different function? It's a lot to add inline for a special case.

Quoted 56 lines

int tcp_sendmsg(struct sock *sk, struct msghdr *msg, int size) {

net/ipv4/tcp.c +39 -0
 	struct iovec *iov;
@@ -1015,6 +1051,7 @@
 	int mss_now;
 	int err, copied;
 	long timeo;
+	int has_sendpage = sk->socket->file->f_op->sendpage != NULL;
 
 	tp = &(sk->tp_pinfo.af_tcp);
 
@@ -1049,6 +1086,44 @@
 
 		iov++;
 
+		if (seglen >= PAGE_SIZE && has_sendpage) {
+			struct vm_area_struct *vma =
+				find_vma (current->mm, (long) from);
+			struct file *filp;
+
+			if (vma && (filp = vma->vm_file)) {
+				read_descriptor_t desc;
+				struct inode *in, *out;
+				loff_t pos = (long) from - vma->vm_start;
+
+				in  = filp->f_dentry->d_inode;
+				out = sk->socket->file->f_dentry->d_inode;
+
+				if (locks_verify_area (FLOCK_VERIFY_READ, in,
+					filp, filp->f_pos, seglen))
+					goto out_no_zero_copy;
+
+				if (locks_verify_area (FLOCK_VERIFY_WRITE, out,
+					sk->socket->file, 0, seglen))
+					goto out_no_zero_copy;
+
+				desc.written = 0;
+				desc.count   = seglen;
+				desc.buf     = (char *) sk;
+				desc.error   = 0;
+
+				do_generic_file_read (filp, &pos, &desc,
+					file_send_actor);
+
+				if (!desc.written) {
+					err = desc.error;
+					goto do_error;
+				}
+				copied += desc.written;
+				continue;
+			}
+		}
+out_no_zero_copy:
 		while (seglen > 0) {
 			int copy;
 
Signature · 2 lines
---
Larry McVoy            	 lm at bitmover.com           http://www.bitmover.com/lm 
#2 lore raw
Quoted 2 lines · Larry McVoy

How about putting this into a different function? It's a lot to add inline for a special case.

This patch also has a ton of other problems:

1) Does not handle writes that straddle multiple VMAs
2) We do not want to encourage people to use this mmap scheme anyways. The mmap way consumes precious VM space, whereas the sendfile scheme does not.
3) Finally, I'm very dubious about the "this is faster than TUX claim". Firstly because you've not provided your self-made HTTP server so that others can try to reproduce your benchmark. And secondly because you haven't indicated if your self-made HTTP server is as full featured as TUX or not. And thirdly you haven't indicated what happens if in parallel clients ask to be served more files than you could mmap fit into the HTTP server processes address space (ie. see #2)

So I think this patch stinks :)

#3 lore raw
Quoted 3 lines · David S. Miller
Quoted 2 lines · Larry McVoy

How about putting this into a different function? It's a lot to add inline for a special case.

All right.

Quoted 1 line

1) Does not handle writes that straddle multiple VMAs

What exactly do you mean? In my test, files larger than a page were handled perfectly, as well.

Quoted 3 lines

2) We do not want to encourage people to use this mmap scheme anyways. The mmap way consumes precious VM space, whereas the sendfile scheme does not.

Is that the answer to my "sendfile is now obsolete"?

Sure we cannot remove sendfile now, as some applications depends on it, but that's not what I wanted.

I made this patch, so that _portable_ applications (and sendfile is miles away from beeing portable - even if the target has a sendfile systemcall, its highly unlikely that it has the same semantics as Linux' sendfile) are sped up.

However, I didn't like the VM waste either, but I believe there is no other way.

Quoted 9 lines

3) Finally, I'm very dubious about the "this is faster than TUX claim". Firstly because you've not provided your self-made HTTP server so that others can try to reproduce your benchmark. And secondly because you haven't indicated if your self-made HTTP server is as full featured as TUX or not. And thirdly you haven't indicated what happens if in parallel clients ask to be served more files than you could mmap fit into the HTTP server processes address space (ie. see #2)

Hehe. In fact that wasn't a really serious claim. My tests were (as explicitly stated by me) done over the Loopback- Interface. And as far as I know TUX can handle interrupts from the network card directly, which probably makes it by far faster.

As I neither have the time nor the infrastructure to do a real test, I cannot really say whether TUX or my (currently unreleased) Webserver is faster.

BTW: My webservers maps files only once, so there shouldn't be a problem with parallel transfers.

Quoted 1 line

So I think this patch stinks :)

But it worked? If I didn't misunderstood #1 then I don't see a problem for integrating it into the current kernel.

#4 lore raw
Quoted 4 lines
Quoted 1 line

1) Does not handle writes that straddle multiple VMAs

What exactly do you mean? In my test, files larger than a page were handled perfectly, as well.

mmap(file1 at location [a,b)
mmap(file2 at location [b,c)
write(sock, a, (size_t)(c - a));

Quoted 2 lines

However, I didn't like the VM waste either, but I believe there is no other way.

The VM cost hurts. Badly. Imagine that the network costs ZERO. Then the map/unmap/vm ops become the dominating term. That's why it is a fruitless approach, it still has a practical limit which is too low.

Signature · 2 lines
---
Larry McVoy            	 lm at bitmover.com           http://www.bitmover.com/lm 
#5 lore raw
Quoted 3 lines · Larry McVoy

The VM cost hurts. Badly. Imagine that the network costs ZERO. Then the map/unmap/vm ops become the dominating term. That's why it is a fruitless approach, it still has a practical limit which is too low.

It depends how predictable your content is. With a 64bit box and a porn server its probably quite tidy

#6 lore raw

From: Alan Cox <[email protected]>
Date: 02 Jan 2003 23:20:44 +0000

Quoted 3 lines · Larry McVoy

The VM cost hurts. Badly. Imagine that the network costs ZERO. Then the map/unmap/vm ops become the dominating term. That's why it is a fruitless approach, it still has a practical limit which is too low.

It depends how predictable your content is. With a 64bit box and a porn server its probably quite tidy

Let's say you have infinite VM (which is what 64-bit almost is :) then the cost is setting up all of these useless VMAs for each and every file (which is a 1 time cost, ok), and also the VMA lookup each write() call.

With sendfile() all of this goes straight to the page cache directly without a VMA lookup.

#7 lore raw
Quoted 10 lines · David S. Miller

It depends how predictable your content is. With a 64bit box and a porn server its probably quite tidy

Let's say you have infinite VM (which is what 64-bit almost is :) then the cost is setting up all of these useless VMAs for each and every file (which is a 1 time cost, ok), and also the VMA lookup each write() call.

With sendfile() all of this goes straight to the page cache directly without a VMA lookup.

With a nasty unpleasant splat the moment you do modification on the content at all. For static objects sendfile is certainly superior,

#8 lore raw

In article <[email protected]>,

Quoted 7 lines · Alan Cox
Quoted 3 lines · David S. Miller

With sendfile() all of this goes straight to the page cache directly without a VMA lookup.

With a nasty unpleasant splat the moment you do modification on the content at all. For static objects sendfile is certainly superior,

Oh, the "unpleasant splat" happens with the mmap approach too, there's no avoiding it. It can happen with a regular "read()" loop too (if the read happens at the wrong time).

Both mmap and sendfile have the issue that the "splat" can happen every time, while a read() into a private area means that the splat can only happen the first time the web server caches the content. But the read into a private area is also obviously the worst one from a performance standpoint.

There are two ways to avoid the splat:
- lock the file some way before reading/writing to it.
- do all updates to a temp-file, and move the temp-file to the new location.

Those two approaches will fix the "splat" problem _regardless_ of what IO mechanism you use. With that in mind, sendfile() is clearly the one that performs best by far, so..

Linus

#9 lore raw
In reply to Alan Cox

From: Alan Cox <[email protected]>
Date: 03 Jan 2003 00:56:59 +0000

Quoted 2 lines · David S. Miller

With sendfile() all of this goes straight to the page cache directly without a VMA lookup.

With a nasty unpleasant splat the moment you do modification on the content at all. For static objects sendfile is certainly superior,

Sendfile does not protect against content changes to the file contents. We don't lock the pages, we merely grab references to them for the network I/O.

#10 lore raw
In reply to Thomas Ogrisegg

From: Thomas Ogrisegg <[email protected]>
Date: Thu, 2 Jan 2003 23:12:11 +0100

Quoted 1 line

1) Does not handle writes that straddle multiple VMAs

What exactly do you mean?

If I mmap two areas 1 right after another, then do a write of comprising of those two areas, your code will only lookup one of the VMAs.

It's a bug.

Quoted 3 lines

2) We do not want to encourage people to use this mmap scheme anyways. The mmap way consumes precious VM space, whereas the sendfile scheme does not.

Is that the answer to my "sendfile is now obsolete"?

It is a "this patch is unacceptable because" comment.

Sure we cannot remove sendfile now, as some applications depends on it, but that's not what I wanted.

That's not what I'm talking about. I'm saying, making this mmap thing available makes no sense at all.

I made this patch, so that _portable_ applications (and sendfile is miles away from beeing portable - even if the target has a sendfile systemcall, its highly unlikely that it has the same semantics as Linux' sendfile) are sped up.

This isn't a priority for us. People who want the best possible performance can code their apps up to take advantage of sendfile() on systems that have it. (and really, show me how many systems lack a sendfile mechanism these days).

However, I didn't like the VM waste either, but I believe there is no other way.

There is a way, convert to sendfile.

Hehe. In fact that wasn't a really serious claim.

Then don't make such claims.

Quoted 1 line

So I think this patch stinks :)

But it worked? If I didn't misunderstood #1 then I don't see a problem for integrating it into the current kernel.

I think you need to rethink the multiple VMA case in #1, and also understand why I don't want this facility in the tree at all anyways. Apps can convert to sendfile(), and as a result they'll get improved performance on ALL linux kernels, not just the ones with your special patch applied.

#11 lore raw
Quoted 4 lines · David S. Miller

From: Thomas Ogrisegg <[email protected]>
Date: Thu, 2 Jan 2003 23:12:11 +0100

It's a bug.

I see. Ok, that can be fixed easily.

Quoted 5 lines

Sure we cannot remove sendfile now, as some applications depends on it, but that's not what I wanted.

That's not what I'm talking about. I'm saying, making this mmap thing available makes no sense at all.

No. For portable applications it makes great sense.

Quoted 8 lines

I made this patch, so that _portable_ applications (and sendfile is miles away from beeing portable - even if the target has a sendfile systemcall, its highly unlikely that it has the same semantics as Linux' sendfile) are sped up.

This isn't a priority for us. People who want the best possible performance can code their apps up to take advantage of sendfile() on systems that have it.

So you want to chain people to your "propritaery solution"?

Quoted 2 lines

(and really, show me how many systems
lack a sendfile mechanism these days).

What kind of systems are you talking about? Operating systems? Nearly all.

Quoted 4 lines

However, I didn't like the VM waste either, but I believe there is no other way.

There is a way, convert to sendfile.

It might be a bit difficult to convert all applications to sendfile. Especially those for which you don't have the source code.

Quoted 8 lines

But it worked? If I didn't misunderstood #1 then I don't see a problem for integrating it into the current kernel.

I think you need to rethink the multiple VMA case in #1, and also understand why I don't want this facility in the tree at all anyways. Apps can convert to sendfile(), and as a result they'll get improved performance on ALL linux kernels, not just the ones with your special patch applied.

I don't see your point. Applications which really need the performance will switch to sendfile anyway because of the problems with mmap, you mentioned.

My patch is very simple and takes less than 1KB of code but will speed up many applications and doesn't have a real drawback (except when sending "normal" data which is larger than a page - but that shouldn't happen very often).

Yet another advantage of my version is that you can use it in conjunction with writev.

Unfortunately the linux-sendfile is not as good as the HP-UX one. Under HP-UX you can define a "struct iovec" header to be sent before the file is sent.

#12 lore raw
Quoted 3 lines

It might be a bit difficult to convert all applications to sendfile. Especially those for which you don't have the source code.

And the list of applications which do

	sock = socket(...);
	map = mmap(...);
	write(sock, map, bytes);

are? There are not very many that I know of and if you look carefully at the bandwidth graphs in LMbench you'll see why. There is a cross over point where mmap becomes cheaper but it used to be around 16-64K. I don't know what it is now, I doubt it's moved much. I can check if you really want.

Signature · 2 lines
---
Larry McVoy            	 lm at bitmover.com           http://www.bitmover.com/lm 
#13 lore raw
Quoted 11 lines · Larry McVoy

And the list of applications which do

	sock = socket(...);
	map = mmap(...);
	write(sock, map, bytes);

are? There are not very many that I know of and if you look carefully at the bandwidth graphs in LMbench you'll see why. There is a cross over point where mmap becomes cheaper but it used to be around 16-64K. I don't know what it is now, I doubt it's moved much. I can check if you really want.

You may not be doing an mmap a send, its more likely to look like

	page = hash(url);
	memcpy(current_time, page->clock, TIMESIZE);
	write(sock, page->data, page->len);

that changes the breakeven point a lot

Alan

#14 lore raw
Quoted 5 lines · Alan Cox

You may not be doing an mmap a send, its more likely to look like

	page = hash(url);
	memcpy(current_time, page->clock, TIMESIZE);
	write(sock, page->data, page->len);

If your web data rarely changes, it could also be all the files stored in a hashfile database covered by one large mmap, eliminating filesystem overhead (and vma overhead).

Signature · 3 lines
// Gianni Tedesco (gianni at scaramanga dot co dot uk)
lynx --source www.scaramanga.co.uk/gianni-at-ecsc.asc | gpg --import
8646BE7D: 6D9F 2287 870E A2C9 8F60 3A3C 91B5 7669 8646 BE7D
#15 lore raw

From: Gianni Tedesco <[email protected]>
Date: 06 Jan 2003 14:36:19 +0000

If your web data rarely changes, it could also be all the files stored in a hashfile database covered by one large mmap, eliminating filesystem overhead (and vma overhead).

You still would eat a VMA lookup each and every send.

#16 lore raw
In reply to Thomas Ogrisegg
Quoted 3 lines · Thomas Ogrisegg

Unfortunately the linux-sendfile is not as good as the HP-UX one. Under HP-UX you can define a "struct iovec" header to be sent before the file is sent.

Thats a design decision. With TCP_CORK and sensible syscall performance those kind of web specific hacks are not appropriate

#17 lore raw
Quoted 7 lines · Alan Cox
Quoted 3 lines · Thomas Ogrisegg

Unfortunately the linux-sendfile is not as good as the HP-UX one. Under HP-UX you can define a "struct iovec" header to be sent before the file is sent.

Thats a design decision. With TCP_CORK and sensible syscall performance those kind of web specific hacks are not appropriate

Indeed. In case Alan's message wasn't clear: if your syscall overhead is zero then many "optimizations" become superfluous. In fact, those optimizations, one cache miss at a time, tend to be a big part of what makes the syscall layer so heavyweight.

Linux is amazing in that it is basically the only real operating system I know of that has stayed so focussed on making the syscall layer be almost invisible. it's worth a "rah rah" because you can use the operating system like it was libc, there is basically very little cost in crossing in/out.

Here's the LMbench context switch benchmark running on a 1.6Ghz Athlon:

load free cach swap pgin  pgou dk0 dk1 dk2 dk3 ipkt opkt  int  ctx  usr sys idl
0.67  73M 577M  25M   0     0    0   0   0   0  4.0  2.0  107  548K  23  77   0
0.67  73M 577M  25M   0     0    0   0   0   0  2.0  2.0  105  549K  19  81   0
0.67  73M 577M  25M   0     0    0   0   0   0  4.0  2.0  107  549K  27  73   0
0.70  73M 577M  25M   0     0    0   0   0   0  2.0  2.0  105  548K  23  77   0

Yeah, that's more than a half a million context switchs/second and each of those include 2 system calls. So Linux is doing 2 system calls and a context switch in 1.8 microseconds.

When you can get in and out of the kernel that fast, your thinking should change. You get to use the kernel more freely. And you certainly don't want to do anything to screw that up. My hat is off to Linus and team for working so hard to make these numbers be so good (and keep on working, see the recent syscall discussion).

Signature · 2 lines
---
Larry McVoy            	 lm at bitmover.com           http://www.bitmover.com/lm 
#18 lore raw
In reply to Thomas Ogrisegg

From: Thomas Ogrisegg <[email protected]>
Date: Fri, 3 Jan 2003 01:45:43 +0100

Quoted 3 lines

This isn't a priority for us. People who want the best possible performance can code their apps up to take advantage of sendfile() on systems that have it.

So you want to chain people to your "propritaery solution"?

I don't hide my APIs.

Quoted 2 lines

(and really, show me how many systems
lack a sendfile mechanism these days).

What kind of systems are you talking about? Operating systems? Nearly all.

HPUX has it, Solaris has it, Microsoft has something very similar, FreeBSD has it as does I believe NetBSD. Show me the exceptions.

It might be a bit difficult to convert all applications to sendfile. Especially those for which you don't have the source code.

If the performance really must be top notch, someone will invest the time for a given application. Otherwise, if it's not that important enough to port why should it be important enough to put a hack into the OS for it?

I don't see your point. Applications which really need the performance will switch to sendfile anyway because of the problems with mmap, you mentioned.

Right, so why bother with your patch?

My patch is very simple and takes less than 1KB of code but will speed up many applications and doesn't have a real drawback (except when sending "normal" data which is larger than a page - but that shouldn't happen very often).

What about the extra checks you are placing in a fast path?