29.1. 프로그램언어 고(Go)의 메모리 관리 방법

프로그램언어 고(Go)의 가비지 컬렉션 작동 방식

프로그램언어 고(Go)의 가비지 컬렉션은 메모리 관리 기법 중 하나로, 사용하지 않는 메모리를 자동으로 해제하고 회수하는 기능을 말합니다.


package main

import "fmt"

func main() {

  // 1. 변수 선언
  var a int 
  a = 10

  // 2. 변수 사용 
  fmt.Println(a)

  // 3. 변수 범위를 벗어나 값을 사용하지 않음
  // => 가비지 컬렉터가 a 변수의 메모리를 자동 회수
} 

고(Go)는 마크 앤 스위프(Mark and Sweep) 알고리즘을 사용하는 가비지 컬렉터를 구현했습니다. 마크 앤 스위프 알고리즘은 크게 두 단계로 이루어집니다.

첫 번째, ‘마크(Mark)’ 단계에서는 현재 사용 중인 객체를 표시합니다. 루트 객체부터 시작해서 참조하고 있는 객체들을 순회하며 마킹을 합니다.

두 번째, ‘스위프(Sweep)’ 단계에서 마킹되지 않은 객체들을 수집 대상으로 파악합니다. 그리고 나서 수집된 객체의 메모리를 해제합니다.

즉, 사용 중인 객체를 마킹하고 사용하지 않는 객체를 수집해 메모리 해제를 하는 방식입니다. 이 과정은 프로그램 실행 중 주기적으로 반복 실행됩니다.

고(Go)의 가비지 컬렉터는 병렬로 동작하는 3개의 가비지 컬렉터로 구성되어 있습니다. 각각은 고르루틴(Goroutine)의 스택, 메모리 동적 할당(Heap), 기타 데이터를 담당합니다.

이 3개의 컬렉터가 함께 동작하면서 사용하지 않는 메모리 영역을 효과적으로 제거 및 회수합니다. 개발자가 따로 메모리 해제 코드를 작성할 필요가 없어 편리합니다.

고 가비지 컬렉터의 이런 작동 원리를 잘 숙지하고 활용한다면 효율적인 메모리 관리가 가능합니다.

프로그램언어 고(Go)에서의 메모리 접근 및 할당 방법

Go 언어에서 메모리 접근 및 할당은 주로 다음과 같은 방법들을 사용합니다.


package main

import "fmt"

func main() {

    // 1. new 함수를 사용한 할당
    ptr := new(int) 
    *ptr = 10
    fmt.Println(*ptr) // 10

    // 2. make 함수를 사용한 할당
    slice := make([]int, 5)
    slice[0] = 100 
    fmt.Println(slice[0]) // 100

    // 3. 리터럴을 사용한 할당
    var num int = 15
    fmt.Println(num) // 15

    // 4. 변수 선언만으로 기본값 할당
    var str string
    fmt.Println(str) // ""
}

주요한 메모리 할당 방법들은 new, make 함수를 사용하는 것과 리터럴을 직접 사용하는 것입니다.

new 함수는 주로 포인터 연산에 사용되며, make 함수는 슬라이스, 맵, 채널과 같은 내장 자료구조들을 초기화하는데 사용됩니다.

리터럴을 사용하면 해당 타입의 기본 값이 할당되며, 변수만 선언하고 값을 할당하지 않으면 해당 타입의 기본값이 자동으로 할당됩니다.

예제의 주석을 통해 각 할당 방식의 특징과 할당된 값을 확인할 수 있습니다.

메모리 접근의 경우 포인터를 사용하거나, 슬라이스/맵과 같은 자료구조의 인덱스 사용, 구조체의 필드 접근 등으로 이루어집니다.

위 예제에서 *ptr, slice[0]이 주요한 메모리 접근 방법들입니다.

이상으로 Go언어의 메모리 할당과 접근에 대한 기본 내용들을 설명해드렸습니다. 부족한 점은 양해 부탁드리겠습니다.

프로그램언어 고(Go)에서의 메모리 누수 방지법

package main

import "fmt"

// User 구조체 정의
type User struct {
    name string
}

func main() {
    // User 인스턴스 생성
    u := User{name: "홍길동"} 

    // u 인스턴스 사용
    fmt.Println(u.name)

    // u 인스턴스 해제를 위해 nil로 설정
    u = User{}
}

Go 언어에서 메모리 누수를 방지하기 위해서는 주로 다음과 같은 방법들을 사용합니다.

첫째, 不要な 변수를 수동으로 nil로 설정하여 가비지 컬렉션 대상이 되도록 만드는 것입니다. 위의 예제코드에서 보다시피 main 함수 내에서 User 인스턴스를 생성한 후 사용했습니다. 사용 후에는 해당 인스턴스를 nil로만든 User{}으로 덮어씌워 가비지 컬렉션 대상이 되도록 했습니다.

둘째, closures 내에서 不要な 변수 사용을 최대한 자제합니다. closure 함수 내에서 선언된 변수들은 함수 실행 완료 이후에도 메모리에 남아있게 되어 누수의 원인이 될 수 있습니다.

셋째, goroutines을 사용할 때 정상 종료 처리를 위한 sync 그룹을 사용하는 것도 도움이 됩니다. WaitGroup을 사용하면 goroutine들이 모두 완료될 때까지 기다릴 수 있어, 정상 적으로 자원을 정리하고 프로그램을 종료시킬 수 있습니다.

이외에도 가비지 컬렉션 주기를 조정하는 GC 튜닝 기법들도 활용 가능합니다. 메모리 사용량과 CPU 부하를 고려하여 적절한 GC 실행 주기를 설정하는 것이 좋습니다.

마지막으로 메모리 프로파일러 툴들을 사용하여 실제 애플리케이션의 메모리 사용 상황을 지속적으로 모니터링 하는 것도 중요합니다. 이를 통해 누수 지점을 발견하고 개선할 수 있습니다.

프로그램언어 고(Go)의 메모리 재사용 팁

프로그램언어 고(Go)에서 메모리 재사용에 대한 팁을 알려드리겠습니다. 고(Go)에서는 가비지 컬렉션(Garbage Collection) 기능을 통해 사용하지 않는 메모리를 자동으로 해제합니다. 그러나 프로그램 성능 최적화를 위해서는 개발자가 직접 메모리 재사용을 위한 코딩을 하는 것이 좋습니다.

주요한 메모리 재사용 팁은 다음과 같습니다.

1. 값 타입을 포인터 대신 사용

type myStruct struct {
    name string
    id   int
}

func main() {
    var a myStruct 
    // sturct는 값 타입으로 전달됨
}

값 타입인 struct를 직접 전달할 경우 메모리 복사가 일어나 데이터가 새로 생성됩니다. 포인터 대신 값 타입을 사용하면 불필요한 메모리 사용을 피할 수 있습니다.

2. defer 함수 최소화

func main() {
    file, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE, 0755)
    defer file.Close()
    
    // 파일 처리 로직
}

defer는 함수 종료시까지 호출을 지연시키기 때문에 메모리와 CPU 사이클이 소모됩니다. 불필요한 defer 사용은 피하는 것이 좋습니다.

3. 매개변수 전달 시 포인터 활용

func largeParamFunction(largeParam *LargeStruct) {
   // 파라미터 처리 로직
}

func main() {
   data := &LargeStruct{} 
   largeParamFunction(data)
}

큰 크기의 매개변수를 전달할 때는 값 대신 포인터를 전달하는 것이 효율적입니다.

위와 같은 메모리 재사용 팁을 통해 고(Go)에서 메모리를 효과적으로 사용할 수 있습니다. 필요에 따라 적재적소에 적용하시기 바랍니다.

프로그램언어 고(Go)에서의 효율적인 슬라이스 메모리 사용 방법

Go언어에서 슬라이스를 메모리 효율적으로 사용하는 방법은 주로 다음과 같습니다.


package main

import "fmt"

func main() {

    // 슬라이스 정의 시 길이와 용량을 지정하지 않음
    slice := []int{} 

    // Append로 항목 추가 시 용량 자동 확장
    slice = append(slice, 1)

    // 필요한 만큼의 용량 할당
    slice = make([]int, 5, 10)

    fmt.Println(len(slice), cap(slice))
}

우선 슬라이스를 정의할 때 초기 길이나 용량을 지정하지 않습니다. 이렇게 하면 내부적으로 가변적으로 메모리를 할당받아 효율적으로 사용할 수 있습니다.

그리고 append 함수를 사용하여 항목을 추가할 때마다 용량이 자동으로 늘어나기 때문에 별도의 용량 설정이 필요 없습니다.

다만 사용 용도에 따라 충분한 용량을 한번에 할당받고 사용할 수도 있습니다. 이렇게 하면 불필요한 메모리 재할당이 발생하지 않습니다.

예제에서 보듯이 make 함수로 슬라이스를 생성할 때 초기 길이와 용량을 지정할 수 있습니다. 이 경우 용량만큼 미리 메모리가 할당되기 때문에 효율적입니다.

이외에도 슬라이스 복사 시 길이와 용량 모두 복사되므로 주의가 필요합니다. 복사를 피하거나 복사 범위를 최소화하는 것이 좋습니다.

드린 설명이 도움이 되셨길 바랍니다.

Leave a Comment