본문 바로가기
Language/Go

Effective Go - Array and Slice

by ocwokocw 2022. 5. 8.

- 출처: https://go.dev/doc/effective_go#arrays

- Arrays

Go에서 Arrays는 주로 slice를 위한 block을 할당하는데 사용된다. Go와 C에서 array가 동작하는 방식의 주요 차이점은 아래와 같다.
  • Arrays는 value 이므로 하나의 array를 다른 array에 할당하면 모든 요소들이 복사된다.
  • 특히 array는 함수에 넘기면 함수는 해당 array의 pointer가 아니라 array의 복사본을 받는다.
  • Array의 크기는 해당 type의 요소이므로 [10]int와 [20]int은 다르다.
 
Value 특성은 유용하게 사용되긴 하지만 비용이 크다. 만약 C와 동일한 동작방식과 효율성을 원한다면 아래 코드처럼 array에 대한 pointer를 넘길 수 있다.
 
func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array)  // Note the explicit address-of operator
 
하지만 위와 같은 사용법은 Go에서 관용적으로 사용되는 스타일은 아니므로 slice를 사용하는것을 추천한다.

- Slices

Slice는 data 순열에 대해 편리하고 강력하며 더 일반적인 인터페이스를 제공하기 위해 array를 감싼것이다. 행렬을 변환하는것과 같은 차원을 다루는것이 아니라면 Go 에서 대부분의 array 프로그래밍은 단순히 array 보다는 slice 를 사용한다.
 
Slice는 원본 array 참조를 갖고 있어서 만약 하나의 slice를 다른 slice에 할당하면 두 slice는 같은 array를 참조하게 된다. 만약 함수가 slice 인자를 받으면 array에 대한 pointer를 전달하는것과 비슷하게 slice 요소들의 변경사항이 호출자에게 표시된다. 그래서 Go의 File을 읽는 Read와 같은 함수는 pointer나 count를 받지 않고 slice 인자를 받는다. slice안에 있는 length는 얼마나 많은 데이터를 읽을 수 있는지에 대한 상한선을 정한다. 아래는 os package의 File형의 Read 메소드 시그니처이다.
 
func (f *File) Read(buf []byte) (n int, err error)
 
위의 메소드는 읽은 bytes의 수와 error값이 있다면 이를 반환한다. 버퍼 buf의 처음 32 바이트를 읽으려면 해당 버퍼를 잘라야 한다.
 
n, err := f.Read(buf[0:32])
 
효율성을 신경쓰지 않는다면 아래 코드도 처음 32바이트를 읽는 동작을 수행한다.
 
var n int
    var err error
    for i := 0; i < 32; i++ {
        nbytes, e := f.Read(buf[i:i+1])  // Read one byte.
        n += nbytes
        if nbytes == 0 || e != nil {
            err = e
            break
        }
    }
 
만약 slice 자기 자신을 할당하는 경우 slice의 길이는 기본 array 의 상한선을 벗어나지 않는 범위내에서 변경될 수 있다. built-in 함수 cap을 통해서 slice가 다룰 수 있는 최대 길이, 용량(capacity)을 알 수 있다. 아래 코드는 slice에 data를 추가하는 함수이다. 만약 data가 capacity를 초과하면 slice는 재할당된다. 결과 slice는 반환된다. 아래 함수에서 len과 cap built-in 함수는 nil slice를 인자로 넘겨도 오류가 나지 않고 0을 반환한다.
 
func Append(slice, data []byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2)
        // The copy function is predeclared and works for any slice type.
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    copy(slice[l:], data)
    return slice
}
 
이때 slice를 반드시 반환해야 하는데 비록 Append 함수가 slice의 요소를 수정할 수는 있지만 slice 자체는 값에 의해 전달되었기 때문이다.
 
slice에 데이터를 추가하는 아이디어는 매우 유용하며 append built-in 함수에서 이를 볼 수 있다. 해당 함수의 설계를 이해하기 위해서는 더 많은 정보가 필요하므로 나중에 다루도록 한다.

- Two-dimensional slices

Go의 array와 slice는 1차원이다. 2차원의 array나 slice를 만들기 위해서는 array의 array나 slice의 slice를 정의해야 한다.
 
type Transform [3][3]float64  // A 3x3 array, really an array of arrays.
type LinesOfText [][]byte     // A slice of byte slices.
 
slice는 길이가 가변이기 때문에, 각 slice는 내부적으로 다양한 길이를 가질 수 있다. 아래 코드에서 LinesOfText는 각 line 별로 독립적인 길이를 갖고 있다.
 
text := LinesOfText{
	[]byte("Now is the time"),
	[]byte("for all good gophers"),
	[]byte("to bring some fun to the party."),
}
 
때로는 2차원 slice를 할당해야 하는 경우가 있는데, 예를 들어 pixel의 line을 스캔처리하는 경우가 그렇다. 이를 처리하는 방법에는 2가지가 있다. 하나는 각 slice를 독립적으로 할당하는것이고, 다른 하나는 array를 할당하고 개별적인 slice들이 배열을 가리키도록 하는것이다. 어떤 방법을 사용할지는 프로그램의 특성에 따라 다르다. 만약 slice가 늘어나거나 줄어들 수 있는 경우 다음 line을 덮어쓰는것을 피하기 위해 독립적으로 할당되어야 한다. 만약 그렇지 않은 경우라면(slice가 가변이 아닌 경우) 단일 할당으로 객체를 구성하는것이 더 효율적인 동작이 될 수 있다. 아래 2 가지 코드는 각 방법에 대한 코드를 나타낸다. 우선 한번에 1 라인씩 할당하는 방법을 할아보자.
 
// Allocate the top-level slice.
picture := make([][]uint8, YSize) // One row per unit of y.
// Loop over the rows, allocating the slice for each row.
for i := range picture {
	picture[i] = make([]uint8, XSize)
}
 
단일 할당하는 경우 아래와 같이 작성할 수 있다.
 
// Allocate the top-level slice, the same as before.
picture := make([][]uint8, YSize) // One row per unit of y.
// Allocate one large slice to hold all the pixels.
pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.
// Loop over the rows, slicing each row from the front of the remaining pixels slice.
for i := range picture {
	picture[i], pixels = pixels[:XSize], pixels[XSize:]
}
 
 
 

'Language > Go' 카테고리의 다른 글

Effective Go - Interfaces and other types  (0) 2022.05.26
Effective Go - Methods  (0) 2022.05.17
Effective Go - New 와 Make  (0) 2022.05.07
Effective Go - Functions  (0) 2022.05.07
Go - Reading files  (0) 2022.03.13

댓글