From: Alexander Bluhm Subject: tcp output copy data To: tech@openbsd.org Date: Thu, 23 Jul 2026 23:39:45 +0200 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 */