[Go]

[Go] 구조체를 여러가지 방법으로 깊은 복사 하는 방법 + 성능비교 (How to deep copy a struct in various ways + Performance comparison)

dogfootman 2024. 6. 25. 22:02

개요

구조체를 여러가지 방법으로 깊은 복사

각 복사 방법의 성능 비교 (Benchmark)

벤치마크 참고: https://dogfootman.com/14

 

[Go] Benchmark 사용법 (How to Use Benchmarks)

개요Benchmark의 사용법   1. 기본 사용법func BenchmarkXxx(*testing.B) 의 형태를 가지고 있으면 벤치마크로 간주되며-bench 의 플러그가 있으면 go test 의 명령에 의해서 실행된다  샘플코드package main...f

dogfootman.com

 

 

1. 수동으로 깊은 복사

package main

import "fmt"

type Address struct {
    City  string
    State string
}

type Student struct {
    Name    string
    Age     int
    Hobbies []string
    Address Address
}

func deepCopyStudent(original Student) Student {
    hobbiesCopy := make([]string, len(original.Hobbies))
    copy(hobbiesCopy, original.Hobbies)

    return Student{
       Name:    original.Name,
       Age:     original.Age,
       Hobbies: hobbiesCopy,
       Address: Address{
          City:  original.Address.City,
          State: original.Address.State,
       },
    }
}

func main() {
    original := Student{
       Name:    "Alice",
       Age:     21,
       Hobbies: []string{"Reading", "Cycling", "Swimming"},
       Address: Address{
          City:  "San Francisco",
          State: "CA",
       },
    }

    // deep copy
    copyStudent := deepCopyStudent(original)

    // update original
    original.Name = "Bob"
    original.Hobbies[0] = "Running"
    original.Address.City = "Los Angeles"

    fmt.Println("Original:", original)
    fmt.Println("Copy:", copyStudent)
}

 

result: 

Original: {Bob 21 [Running Cycling Swimming] {Los Angeles CA}}
Copy: {Alice 21 [Reading Cycling Swimming] {San Francisco CA}}

 

 

2. Json을 이용한 깊은 복사

package main

import (
    "encoding/json"
    "fmt"
)

type Address struct {
    City  string
    State string
}

type Student struct {
    Name    string
    Age     int
    Hobbies []string
    Address Address
}

func deepCopyStudent(original Student) (Student, error) {
    var copyStudent Student
    data, err := json.Marshal(original)
    if err != nil {
       return copyStudent, err
    }
    err = json.Unmarshal(data, &copyStudent)
    return copyStudent, err
}

func main() {
    original := Student{
       Name:    "Alice",
       Age:     21,
       Hobbies: []string{"Reading", "Cycling", "Swimming"},
       Address: Address{
          City:  "San Francisco",
          State: "CA",
       },
    }

    // deep copy
    copyStudent, err := deepCopyStudent(original)
    if err != nil {
       fmt.Println("Error:", err)
       return
    }

    // update original
    original.Name = "Bob"
    original.Hobbies[0] = "Running"
    original.Address.City = "Los Angeles"

    fmt.Println("Original:", original)
    fmt.Println("Copy:", copyStudent)
}

 

result: 

Original: {Bob 21 [Running Cycling Swimming] {Los Angeles CA}}
Copy: {Alice 21 [Reading Cycling Swimming] {San Francisco CA}}

 

 

3. 리플렉션을 이용한 깊은 복사

package main

import (
    "fmt"
    "reflect"
)

type Address struct {
    City  string
    State string
}

type Student struct {
    Name    string
    Age     int
    Hobbies []string
    Address Address
}

func deepCopy(dst, src interface{}) {
    srcVal := reflect.ValueOf(src)
    dstVal := reflect.ValueOf(dst).Elem()
    deepCopyValue(dstVal, srcVal)
}

func deepCopyValue(dst, src reflect.Value) {
    switch src.Kind() {
    case reflect.Ptr:
       if !src.IsNil() {
          dst.Set(reflect.New(src.Elem().Type()))
          deepCopyValue(dst.Elem(), src.Elem())
       }
    case reflect.Struct:
       for i := 0; i < src.NumField(); i++ {
          deepCopyValue(dst.Field(i), src.Field(i))
       }
    case reflect.Slice:
       if !src.IsNil() {
          dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))
          for i := 0; i < src.Len(); i++ {
             deepCopyValue(dst.Index(i), src.Index(i))
          }
       }
    default:
       dst.Set(src)
    }
}

func main() {
    original := Student{
       Name:    "Alice",
       Age:     21,
       Hobbies: []string{"Reading", "Cycling", "Swimming"},
       Address: Address{
          City:  "San Francisco",
          State: "CA",
       },
    }

    // deep copy
    var copyStudent Student
    deepCopy(&copyStudent, original)

    // update original
    original.Name = "Bob"
    original.Hobbies[0] = "Running"
    original.Address.City = "Los Angeles"

    fmt.Println("Original:", original)
    fmt.Println("Copy:", copyStudent)
}

 

result:

Original: {Bob 21 [Running Cycling Swimming] {Los Angeles CA}}
Copy: {Alice 21 [Reading Cycling Swimming] {San Francisco CA}}

 

 

4. Gob을 이용한 깊은 복사

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
)

type Address struct {
    City  string
    State string
}

type Student struct {
    Name    string
    Age     int
    Hobbies []string
    Address Address
}

func deepCopyStudent(original Student) (Student, error) {
    var copyStudent Student
    buf := new(bytes.Buffer)
    enc := gob.NewEncoder(buf)
    dec := gob.NewDecoder(buf)
    if err := enc.Encode(original); err != nil {
       return copyStudent, err
    }
    if err := dec.Decode(&copyStudent); err != nil {
       return copyStudent, err
    }
    return copyStudent, nil
}

func main() {
    original := Student{
       Name:    "Alice",
       Age:     21,
       Hobbies: []string{"Reading", "Cycling", "Swimming"},
       Address: Address{
          City:  "San Francisco",
          State: "CA",
       },
    }

    // deep copy
    copyStudent, err := deepCopyStudent(original)
    if err != nil {
       fmt.Println("Error:", err)
       return
    }

    // update original
    original.Name = "Bob"
    original.Hobbies[0] = "Running"
    original.Address.City = "Los Angeles"

    fmt.Println("Original:", original)
    fmt.Println("Copy:", copyStudent)
}

 

result:

Original: {Bob 21 [Running Cycling Swimming] {Los Angeles CA}}
Copy: {Alice 21 [Reading Cycling Swimming] {San Francisco CA}}

 

 

5. 성능 비교

실행 커맨드

go test -bench="." -benchmem

 

실행 코드

main_test.go
0.00MB

 

방법 실행 횟수 (1초) 평균 실행 속도 평균 메모리 사용량 메모리 할당 수
수동 35818543 32.92 ns/op 48 B/op 1 allocs/op
리플렉션 4314712 272.6 ns/op 232 B/op 4 allocs/op
JSON 518389 2220 ns/op 720 B/op 18 allocs/op
Gob 71022 17085 ns/op 10284 B/op 261 allocs/op

 

해당 테스트 코드에서의 속도는

수동 > 리플렉션 > JSON > Gob

의 순서로 빠르고 메모리 사용량도 적다