Contents

Go: io.ReadAll vs io.Copy

When writing Go, we often encounter situations where we need to read files or responses from API calls. Generally, io.ReadAll and io.Copy are used for this. This post will explore the differences between them.

io.ReadAll

io.ReadAll is a function used to read all data from a Reader and return it as a byte slice ([]byte) of a fixed size containing all the data read into memory. Using io.ReadAll might be suitable for reading small amounts of data because you get a byte slice to work with directly. However, since it loads all the data into memory, it’s not suitable for reading large files.

Example

file, _ := os.OpenFile("small-file.json", os.O_RDONLY, 0664)
defer file.Close()
bodyBytes, err := io.ReadAll(file)

io.Copy

io.Copy is used for copying data from a Reader to a Writer without needing to store all the data in memory. When data is received from the Reader, it copies it to the Writer in chunks, allowing it to handle very large amounts of data without burdening memory.

Example

file, _ := os.OpenFile("small-file.json", os.O_RDONLY, 0664)
defer file.Close()
bytesBuff := new(bytes.Buffer)
_,err := io.Copy(bytesBuff, file)
bodyBytes := bytesBuff.Bytes()

Performance Comparison

Writing the test

We will test by benchmarking the reading of a JSON file.

import (
	"os"
	"bytes"
	"testing"
)

func BenchmarkReadAll(b *testing.B) {

	b.ReportAllocs()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			file, _ := os.Open("file.json")
			defer file.Close()
			io.ReadAll(file)
		}
	})
}

func BenchmarkCopy(b *testing.B) {

	b.ReportAllocs()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			file, _ := os.Open("file.json")
			defer file.Close()
			bytesBuff := new(bytes.Buffer)
			io.Copy(bytesBuff, file)
		}
	})
}

Test Results

Test results for reading small 10KB, medium 2.9MB, and large 26MB files.

Name Loops Executed Time Taken per Iteration Bytes Allocated per Operation Allocations per Operation
BenchmarkReadAllSmall-8 45658 24383 ns/op 46296 B/op 14 allocs/op
BenchmarkCopySmall-8 64654 16235 ns/op 30938 B/op 11 allocs/op
BenchmarkReadAllMedium-8 1510 734884 ns/op 16792061 B/op 37 allocs/op
BenchmarkCopyMedium-8 3433 333917 ns/op 8388372 B/op 20 allocs/op
BenchmarkReadAllLarge-8 171 6335655 ns/op 160741794 B/op 46 allocs/op
BenchmarkCopyLarge-8 237 7064719 ns/op 67108578 B/op 22 allocs/op

We can see that io.Copy is, on average, about 40% more performant than io.ReadAll. However, this comes at the cost of writing more code to create a buffer to receive the data, which might not appeal to the lazy programmer 🤣🤣🤣