Download raw body.
tcp output copy data
Hi,
My benchmarks show that copy mbuf by refererence is expensive. Long
chains need many new mbufs allocation to hold each cluster reference
in the TCP send socket buffer. During free I see contention on the
cluster ref count.
Especially socket splicing creates long chains of 2k clusters. When
writing from userland to socket buffer, we already allocate large
clusters.
Idea of the diff is to detect many short clusters when creating
large TSO packets. The heuristic "M_SIZE(so->so_snd.sb_mb) * 5 <
len" might be done a bit smarter. In this case we allocate one
large cluster where all data fits. There are no references and
freeing in the driver will be cheap. The memcpy() in m_copydata()
is less effort than allocate, refcount, free. Always copy data to
the end of the cluster to keep maximum space for headers.
With socket splicing this increases througput from 55 GBit/sec to
70 GBit/sec in my setup.
What do you think of this idea?
bluhm
Index: kern/uipc_mbuf.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/kern/uipc_mbuf.c,v
diff -u -p -r1.307 uipc_mbuf.c
--- kern/uipc_mbuf.c 3 Jul 2026 11:51:57 -0000 1.307
+++ kern/uipc_mbuf.c 23 Jul 2026 19:27:00 -0000
@@ -156,11 +156,6 @@ struct pool_allocator m_pool_allocator =
static void (*mextfree_fns[4])(caddr_t, u_int, void *);
static u_int num_extfree_fns;
-#define M_DATABUF(m) ((m)->m_flags & M_EXT ? (m)->m_ext.ext_buf : \
- (m)->m_flags & M_PKTHDR ? (m)->m_pktdat : (m)->m_dat)
-#define M_SIZE(m) ((m)->m_flags & M_EXT ? (m)->m_ext.ext_size : \
- (m)->m_flags & M_PKTHDR ? MHLEN : MLEN)
-
/*
* Initialize the mbuf allocator.
*/
Index: netinet/tcp_output.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_output.c,v
diff -u -p -r1.158 tcp_output.c
--- netinet/tcp_output.c 1 Jan 2026 05:28:23 -0000 1.158
+++ netinet/tcp_output.c 23 Jul 2026 19:30:15 -0000
@@ -667,8 +667,18 @@ send:
tcpstat_pkt(tcps_sndpack, tcps_sndbyte, len);
}
MGETHDR(m, M_DONTWAIT, MT_HEADER);
- if (m != NULL && max_linkhdr + hdrlen > MHLEN) {
- MCLGET(m, M_DONTWAIT);
+ if (m != NULL && (max_linkhdr + hdrlen > MHLEN ||
+ M_SIZE(so->so_snd.sb_mb) * 5 < len)) {
+ /*
+ * m_copym() many small clusters is expensive.
+ * It costs pool get and ref count performance.
+ * Allocate a large cluster and copy all data.
+ */
+ if (M_SIZE(so->so_snd.sb_mb) * 5 < len) {
+ MCLGETL(m, M_DONTWAIT,
+ max_linkhdr + hdrlen + len);
+ } else
+ MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_freem(m);
m = NULL;
@@ -681,10 +691,14 @@ send:
m->m_data += max_linkhdr;
m->m_len = hdrlen;
if (len <= m_trailingspace(m)) {
+ m->m_data -= max_linkhdr;
+ m_align(m, hdrlen + len);
m_copydata(so->so_snd.sb_mb, off, (int) len,
mtod(m, caddr_t) + hdrlen);
m->m_len += len;
} else {
+ m->m_data -= max_linkhdr;
+ m_align(m, hdrlen);
m->m_next = m_copym(so->so_snd.sb_mb, off, (int) len,
M_NOWAIT);
if (m->m_next == 0) {
Index: sys/mbuf.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/sys/mbuf.h,v
diff -u -p -r1.271 mbuf.h
--- sys/mbuf.h 3 Jul 2026 11:51:57 -0000 1.271
+++ sys/mbuf.h 23 Jul 2026 19:27:10 -0000
@@ -63,6 +63,11 @@
/* 2K cluster can hold Ether frame */
#define MCLBYTES (1 << MCLSHIFT) /* size of a m_buf cluster */
+#define M_DATABUF(m) ((m)->m_flags & M_EXT ? (m)->m_ext.ext_buf : \
+ (m)->m_flags & M_PKTHDR ? (m)->m_pktdat : (m)->m_dat)
+#define M_SIZE(m) ((m)->m_flags & M_EXT ? (m)->m_ext.ext_size : \
+ (m)->m_flags & M_PKTHDR ? MHLEN : MLEN)
+
/* Packet tags structure */
struct m_tag {
SLIST_ENTRY(m_tag) m_tag_link; /* List of packet tags */
tcp output copy data