I found an unintuitive phenomenon in the production environment:
- I have a server to accept client request.
- The server read up to 16K of data each time (i.e.
read(fd, buf, 16 * 1024)).
I found that the latency is 2µs when read() system call returns 16KB through strace -c, but the latency is 5µs when read() systemc call returns 10B. (NOTE: server always read 16KB per read() and the difference is client-side. One client writes 16KB at a time and the other writes 10B.)
I think this is counter-intuitive, read() or write() 16KB should be slower than 10B, but i don’t know much about the linux kernel and network stack.
I write a minimal reproducible example:
The server-side code:
#include <assert.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
const int BUFSIZE = 16 * 1024;
int main() {
// make a socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
assert(fd > 0);
// bind to 0.0.0.0:7731
struct sockaddr_in sock;
memset(&sock, 0, sizeof(sock));
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = INADDR_ANY;
sock.sin_port = htons(7731);
bind(fd, (struct sockaddr*)&sock, sizeof(sock));
assert(listen(fd, 128) == 0);
// accept a peer
struct sockaddr_in peer_sock;
socklen_t socklen;
int peer_fd = accept(fd, (struct sockaddr*)&peer_sock, &socklen);
assert(peer_fd > 0);
// read & write
char buf[BUFSIZE];
int nread = 0;
while (nread = read(peer_fd, buf, sizeof(buf)), nread > 0) {
int sum = 0;
for (int i = 0; i < nread; ++i) {
sum += buf[i];
}
assert(write(peer_fd, buf, nread) == nread);
}
return 0;
}
The client-side code (by golang):
package main
import (
"log"
"net"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:7731")
if err != nil {
log.Fatal(err)
}
// 16KB or 10B buf
buf := make([]byte, 16*1024)
for i := 0; i < 1000000; i++ {
nwrite := 0
for nwrite != len(buf) {
n, err := conn.Write(buf)
if err != nil {
log.Fatal(err)
}
nwrite += n
}
nread := 0
readBuf := make([]byte, len(buf))
for nread != len(buf) {
n, err := conn.Read(readBuf)
if err != nil {
log.Fatal(err)
}
nread += n
}
log.Print(i)
}
}
When the client writes 10B at a time (NOTE: server always read 16KB at a time. i.e. read(fd, buf, 16384)), the strace and strace -c of server-side output is:
$ strace ./server
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
read(4, "\0\0\0\0\0\0\0\0\0\0", 16384) = 10
write(4, "\0\0\0\0\0\0\0\0\0\0", 10) = 10
$ strace -c ./a.out
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
65.00 0.233234 9 24596 write
35.00 0.125599 5 24598 read
0.00 0.000000 0 2 open
0.00 0.000000 0 2 close
0.00 0.000000 0 2 fstat
0.00 0.000000 0 5 mmap
0.00 0.000000 0 4 mprotect
0.00 0.000000 0 1 munmap
0.00 0.000000 0 1 brk
0.00 0.000000 0 3 3 access
0.00 0.000000 0 1 socket
0.00 0.000000 0 1 accept
0.00 0.000000 0 1 bind
0.00 0.000000 0 1 listen
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00 0.358833 49220 3 total
When the client writes 16KB at a time, the strace and strace -c of server-side output is:
$ strace ./server
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
write(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 16384) = 16384
$ strace -c ./server
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
61.26 0.063949 3 20692 write
38.74 0.040441 2 20694 read
0.00 0.000000 0 2 open
0.00 0.000000 0 2 close
0.00 0.000000 0 2 fstat
0.00 0.000000 0 5 mmap
0.00 0.000000 0 4 mprotect
0.00 0.000000 0 1 munmap
0.00 0.000000 0 1 brk
0.00 0.000000 0 3 3 access
0.00 0.000000 0 1 socket
0.00 0.000000 0 1 accept
0.00 0.000000 0 1 bind
0.00 0.000000 0 1 listen
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00 0.104390 41412 3 total