GO: io.ReadAll vs io.Copy
การเขียน Go บ่อยครั้งเราจะเจอการที่เราต้องเปิดอ่านไฟล์หรืออ่าน Response จากการดึง API ซึ่งส่วนใหญ่ โดยทั่วไปแล้วก็จะใช้ io.ReadAll และ io.Copy กัน ตอนนี้จะพาไปดูว่ามันแตกต่างกันอย่างไร
io.ReadAll
io.ReadAll
เป็นฟังก์ชันที่ใช้สำหรับอ่านข้อมูลจากที่อ่านได้ (Readers) ทั้งหมดและส่งกลับข้อมูลในรูปแบบของ byte slice (เช่น []byte) ที่มีขนาดคงที่ของข้อมูลทั้งหมดที่ถูกอ่านออกมาในหน่วยความจำ โดยการใช้ io.ReadAll
อาจเหมาะสำหรับการอ่านข้อมูลที่มีขนาดเล็ก เพราะจะได้ byte slice มาใช้งานเลย แต่เนื่องจากการโยนข้อมูลทั้งหมดเก็บไว้ในหน่วยความจำ ทำให้ไม่เหมาะกับการอ่านข้อมูลขนาดใหญ่
ตัวอย่าง
file, _ := os.OpenFile("small-file.json", os.O_RDONLY, 0664)
defer file.Close()
bodyBytes, err := io.ReadAll(file)
io.Copy
io.Copy
ใช้สำหรับการคัดลอกข้อมูลจาก Reader ไปยัง Writer โดยไม่จำเป็นต้องเก็บข้อมูลทั้งหมดในหน่วยความจำ เมื่อมีข้อมูลถูกส่งมาจาก Reader มันจะทำการคัดลอกข้อมูลนั้นไปยัง Writer ทีละส่วน ซึ่งทำให้สามารถทำงานกับข้อมูลที่มีขนาดใหญ่มาก ๆ ได้โดยไม่เป็นภาระต่อหน่วยความจำ
ตัวอย่าง
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()
เปรียบเทียบการทำงาน
เขียนการทดสอบ
โดยจะทดสอบด้วยการทำ Benchmark เทียบการอ่านไฟล์ JSON
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)
}
})
}
ผลการทดสอบ
ผลการทดสอบอ่านไฟล์ small 10KB, medium 2.9MB, large 26MB
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 |
จะพบว่า io.Copy
จะประสิทธิภาพดีกว่า io.ReadAll
เฉลี่ยที่ 40% เลยทีเดียว แต่ก็แลกมากับการที่ต้องเขียนโค้ดเพิ่ม Buffer มารับข้อมูล ซึ่งอาจจะไม่ถูกจริตสายขี้เกียจสักเท่าไหร่ 🤣🤣🤣