I'm seeing occasionally slow performance in writing to a file (or socket) and in debugging, I ended up with a small test harness to reproduce it. I'm seeing strange behavior whereby the longer I sleep in between writes, the longer the write itself takes. I reproduced the test harness in both Go and Python and see similar results in both. I also see similar results on both my mac and linux machines.
The key confusion is that the timing code in both cases looks like
sleep(x)
startTime = now()
write(msg)
endTime = now()
writeTime = endTime - startTime
where writeTime gets significantly longer as the sleep increases, despite the sleep not being included in the timing at all!
Python:
import time
f = open("/dev/null", "w")
msg = "0" * 1024
for sleepMicros in [0, 1, 10, 100, 1000]:
totalSendTime = 0
cntOver10us = 0
for _ in range(10000):
time.sleep(sleepMicros / 1000 / 1000)
startSendTime = time.time()
f.write(msg)
sentTime = time.time()
timeInSend = sentTime - startSendTime
totalSendTime += timeInSend
if timeInSend > 10 / 1000 / 1000:
cntOver10us += 1
print(f"sleep: {sleepMicros}us\tTotal time in send: {totalSendTime * 1000}ms\tCount over 10us: {cntOver10us}")
Go:
package main
import (
"fmt"
"os"
"time"
)
func main() {
f, _ := os.Create("/dev/null")
msg := make([]byte, 1024)
for _, sleepMicros := range []time.Duration{0, 1, 10, 100, 1000} {
totalSendTime := time.Duration(0)
cntOver10us := 0
sleepDuration := sleepMicros * time.Microsecond
for i := 0; i < 10000; i++ {
time.Sleep(sleepDuration)
startSendTime := time.Now()
_, err := f.Write(msg)
sentTime := time.Now()
if err != nil {
fmt.Println("SendMessage got error", err)
}
timeInSend := sentTime.Sub(startSendTime)
totalSendTime += timeInSend
if timeInSend > 10*time.Microsecond {
cntOver10us++
}
}
fmt.Println("sleep:", sleepDuration, "\tTotal time in send:", totalSendTime, "\tCount over 10us:", cntOver10us)
}
}
Results:
Python:
(venv) % python write_to_devnull_mini.py
sleep: 0us Total time in send: 2.109527587890625ms Count over 10us: 0
sleep: 1us Total time in send: 1.9390583038330078ms Count over 10us: 0
sleep: 10us Total time in send: 1.9574165344238281ms Count over 10us: 0
sleep: 100us Total time in send: 5.476236343383789ms Count over 10us: 9
sleep: 1000us Total time in send: 50.98319053649902ms Count over 10us: 1179
Go:
(venv) % go run write_to_devnull_mini.go
sleep: 0s Total time in send: 5.724469ms Count over 10us: 0
sleep: 1µs Total time in send: 6.404582ms Count over 10us: 1
sleep: 10µs Total time in send: 4.346918ms Count over 10us: 0
sleep: 100µs Total time in send: 10.909356ms Count over 10us: 8
sleep: 1ms Total time in send: 112.627213ms Count over 10us: 1307
My best guess is that this is some artifact of the scheduler, but would love to understand what's happening. How does sleeping for 1ms in between writes cause the total time spent between calls to time.Now() to go up by 20x, and the number of "slow" (>10us) writes to increase 200-fold?