본문 바로가기
golang

[TIL] golang buffer 복사

by marble25 2023. 12. 21.

golang에서 buffer를 io.ReadSeeker 인터페이스로 복사해줄 일이 생겼다.

buffer 내용은 이후에 읽고 수정하기 때문에 consume하면 안된다. 내부 데이터만 복사해서 넘겨줘야 했다.

#fail 1: bytes로 reader 만들어주기

data := bytes.NewReader(rw.Buffer().Bytes())

bytes slice를 그대로 reader로 넘겨주었는데, 이 데이터를 수정하는 시점과 data를 읽는 시점에서 timing issue가 생겼다. data를 읽는 함수가 goroutine으로 async로 구현되어 있기 때문이다.

Buffer의 bytes 설명을 보면 다음과 같이 적혀 있다.

The slice aliases the buffer content at least until the next buffer modification, so immediate changes to the slice will affect the result of future reads.

즉 다른 함수에서 buffer를 수정하면 위의 reader에도 동일하게 적용되어 read 시점에는 변경된 데이터를 읽게 된다.

slice가 같은 대상을 reference하지 않도록 완전히 복사를 해 줘야 한다.

찾아보니 써볼만한 방법이 2가지 정도 있었다. 성능 테스트를 위해 약 4.7MB의 버퍼 복사를 5회 테스트해보았다.

#try 1: append

var data *bytes.Reader
for i := 0; i < 5; i++ {
	t1 := time.Now()
	data = bytes.NewReader(append([]byte{}, rw.Buffer().Bytes()...))
	t2 := time.Now()
	logger.Info("copying result buffer", "duration", t2.Sub(t1))
	time.Sleep(1 * time.Second)
}

결과는 0.000438541 / 0.001156042 / 0.000895 / 0.001670416 / 0.000567083 (단위: ns) 가 나왔다.

평균은 0.0009454164 (단위: ns)가 나왔다.

#try 2: copy

var data *bytes.Reader
for i := 0; i < 5; i++ {
	t1 := time.Now()
	b := make([]byte, rw.Buffer().Len())
	copy(b, rw.Buffer().Bytes())
	data = bytes.NewReader(b)
	t2 := time.Now()
	logger.Info("copying result buffer", "duration", t2.Sub(t1))
	time.Sleep(1 * time.Second)
}

결과는 0.000467708 / 0.001694417 / 0.001389625 / 0.001034 / 0.005846375 (단위: ns) 가 나왔다.

평균은 0.002086425 (단위: ns)가 나왔다.

나는 당연히 copy method가 더 시간이 적게 걸릴 것으로 예상했는데, 예상 외로 append가 더 시간이 적게 걸렸다. 코드도 더 간단하고 시간도 적게 걸리는 1번 방법을 선택했다.