출처: https://zenn.dev/mattn/articles/31dfed3c89956d
슬라이스의 길이 늘리기 뿐만 아니라 요소의 제거도 슬라이스 자신을 업데이트해야 한다. Go 언어의 저장소에 있는 Wiki에서 참조하면, Go 언어에서 슬라이스에서 요소를 삭제하려면 두 가지(엄밀하게는 3개)의 방법이 있다.
append를 사용하는 방법
a = append(a[:i], a[i+1:]...) |
append는 첫 번째 인자의 슬라이스에 두 번째 인수 이후의 값을 추가하는 함수이다. 삭제 대상까지의 슬라이스에, 삭제 대상 다음 이후의 슬라이스를 추가하고 결과를 덮어 쓴다.
copy를 사용하는 방법
a = a[:i+copy(a[i:], a[i+1:])] |
copy는 두 개의 인수로 주어진 슬라이스에서 오른쪽 항에서 왼쪽 항에 복사하는 기능이다. 반환 값은 복사 한 개수이다. 이를 이용하여 슬라이스의 제거 대상을 삭제할 다음 이후의 슬라이스로 덮어 쓰고, 준 요소 수로 슬라이스를 재 할당한다. 따라서 마지막 요소가 삭제 되는 것이다.
순서를 생각하지 않는 삭제
a[i] = a[len(a)-1] |
이것은 제거 후의 슬라이스 순서가 바뀌어 버려도 좋을 경우에 사용할 수 있는 방법이다. 마지막 요소를 삭제 대상에 덮어 쓰고, 요소 수를 줄이고 다시 할당한다.
벤치 마크
벤치 마크를 해 보자.
package main_test |
이중 루프로 된 것은 슬라이스의 어느 위치를 지우는 등에 의해 벤치 마크의 차이가 발생한다면 그 결과도 벤치 마크로 측정하고 싶기 때문이다. 슬라이스의 선두에 있는 요소를 지우면, 나머지 조각을 앞으로 당겨야 한다. 그래서 첫 번째 요소를 제거하는데 걸리는 시간은 마지막 요소를 제거하는데 걸리는 시간보다 길어진다.
삭제 후 순서를 보장하지 않는 remove3은 이렇게 당겨야 할 필요 없기 때문에 어떤 위치의 요소를 지워도 처리 속도가 일정하다.
goos: windows
goarch: amd64
pkg: remove
cpu: AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx
BenchmarkRemove1-8 1 1281985400 ns/op 24 B/op 3 allocs/op
BenchmarkRemove2-8 1 1453808700 ns/op 0 B/op 0 allocs/op
BenchmarkRemove3-8 19694 68062 ns/op 0 B/op 0 allocs/op
PASS
ok remove 4.767s
예상대로 remove3이 빠르다. 위의 벤치 마크 소스의 주석 부분을 교체하고, 항상 첫 번째 요소를 지우는 것처럼 하면 아래와 같은 결과가 된다.
goos: windows
goarch: amd64
pkg: remove
cpu: AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx
BenchmarkRemove1-8 1 4713567000 ns/op 16 B/op 2 allocs/op
BenchmarkRemove2-8 1 4537595900 ns/op 0 B/op 0 allocs/op
BenchmarkRemove3-8 18007 64091 ns/op 0 B/op 0 allocs/op
PASS
ok remove 11.220s
remove1과 remove2는 느려졌다. 만약 슬라이스의 순서를 신경 쓰지 않아도 좋은 경우라면 remove3 방법을 사용하면 좋다.
하나 주의해야 것이 있다. 메모리를 확보하지 않는 원시적인 타입이라면 문제 없지만, 예를 들어 new를 사용하는 개체를 포함하는 슬라이스는 크기를 줄인 마지막 요소에 참조가 남게된다. GC에 의해 메모리를 회수하려면
a[i] = a[len(a)-1] |
이와 같이 제로 값을 대입 해두면 좋을 것이다.
그런데 append 방법은
사실 할당이 발생한다. 슬라이스를 전체 요소에 더 할 때 넣는 … 에 의해 일시적으로 메모리가 할당된다. 많은 블로그는 이 append를 사용하는 방법을 많이 소개하고 있다. 가능하면 copy를 사용하는 방법이 더 좋다.
정리
Go 언어에서 슬라이스 요소의 제거 방법을 3가지 소개했다.
또한 순서를 생각하지 않아도 좋은 슬라이스의 경우 성능을 좋게 하는 방법( remove3)을 소개했다. 또 append를 사용한 방법의 경우 메모리 할당이 발생하는 것을 소개했다. 꼭 여러분의 코드를 한 번 검토하고 더 성능 좋은 코드를 만들 수 없는지 생각해 보기 바란다.