Go TCP Benchmark: Single Goroutine Throughput Increases as Total Concurrency Increases

64 views
Skip to first unread message

doubled lin

unread,
Nov 29, 2025, 11:31:49 PM (5 days ago) Nov 29
to golang-nuts
Problem Description

I’m running a simple TCP throughput benchmark using Go 1.24.6.
Both the client and server have abundant CPU, memory, and network resources, and no system-level limits (ulimits, NIC queues, CPU throttling, etc.) are in effect.

However, I’m observing an unexpected behavior:

The throughput of each individual goroutine becomes higher when the overall client concurrency increases.
Example: per-goroutine throughput at concurrency=5 < concurrency=10 < concurrency=15.

This is counterintuitive — I expect each goroutine's performance to remain roughly the same regardless of how many other goroutines are running.

Test Setup Server

The server sends a fixed 64MiB random buffer to every client connection. It uses a 1 MiB write buffer.

Client

The client spawns N concurrent goroutines (concurrency = 5, 10, 15...).
Each goroutine opens a TCP connection and reads until EOF using a 32 MiB buffer.

Observed Behavior
  • When running with 1 goroutine, the read time is longer.

  • As I increase concurrency to 5, 10, 15... the throughput of each individual goroutine improves significantly.

  • This happens even though:

    • CPU is not saturated

    • Network is not saturated

    • Disk is not involved

    • Both machines have very high bandwidth (10 GbE+)

Expected Behavior

Each goroutine should achieve similar throughput regardless of how many goroutines are running concurrently, assuming sufficient system resources.

Test code

1、server:

package main

 

import (

"bytes"

"fmt"

"math/rand"

"net"

"net/http"

_ "net/http/pprof"

"time"

"io"

)

 

const dataSize = 64 * 1024 * 1024 // 64 MiB

 

func main() {

go func() {

fmt.Println("pprof listening on :6060")

http.ListenAndServe("10.58.3.23:6060", nil)

}()

 

ln, err := net.Listen("tcp", "10.58.3.23:9090")

if err != nil {

panic(err)

}

fmt.Println("Server listening on :9090")

 

data := make([]byte, dataSize)

rand.Read(data)

 

for {

conn, err := ln.Accept()

if err != nil {

fmt.Println("Accept error:", err)

continue

}

go handleConn(conn, data)

}

}

 

func handleConn(conn net.Conn, data []byte) {

defer conn.Close()

 

fmt.Println("Client connected:", conn.RemoteAddr())

start := time.Now()

r := bytes.NewReader(data)

_, err := io.Copy(conn, r)

if err != nil {

fmt.Println("Write error:", err)

return

}

 

duration := time.Since(start)

fmt.Printf("Sent %d bytes to %v, duration: %v\n", dataSize, conn.RemoteAddr(), duration)

}


2、client code:

package main

 

import (

"fmt"

"io"

"net"

"sync"

"time"

)

 

const concurrency = 4

 

func main() {

var wg sync.WaitGroup

 

for i := 0; i < concurrency; i++ {

wg.Add(1)

go func(id int) {

defer wg.Done()

start := time.Now()

conn, err := net.Dial("tcp", "10.58.3.23:9090")

if err != nil {

fmt.Printf("[Worker %d] Dial error: %v\n", id, err)

return

}

defer conn.Close()

buf := make([]byte, 64*1024*1024) // 1 MiB buffer

total := 0

for {

n, err := conn.Read(buf)

total += n

if err == io.EOF {

break

}

if err != nil {

fmt.Printf("[Worker %d] Read error: %v\n", id, err)

return

}

}

duration := time.Since(start)

fmt.Printf("[Worker %d] Finished reading %d bytes, duration: %v\n", id, total, duration)

}(i + 1)

}

 

wg.Wait()

fmt.Println("All goroutines completed")

}


Robert Engels

unread,
Nov 30, 2025, 1:57:14 AM (5 days ago) Nov 30
to doubled lin, golang-nuts

This is expected. If there is little work to do the scheduler needs to park routines then wake them up and schedule them on a thread - and the OS needs to wake and schedule these threads as well. 

With more work to do there is less “parking” thus it is more efficient. 

On Nov 29, 2025, at 10:30 PM, doubled lin <lindo...@gmail.com> wrote:

Problem Description
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/f307dd0e-cc9d-434e-a452-8e367e987f3bn%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages