[C#] struct의 메모리 복사 비용

Date:     Updated:

카테고리:

태그:

struct 타입의 메모리 복사 성능에 대해 테스트하고 작성한 글이다.

struct의 메모리 복사 비용

  • struct는 기본적으로 크기와 관계없이 Stack에 할당된다
    • 일정 크기 이상일 경우 Heap에 할당된다는 정보가 있어 유니티에서 1024kb 크기의 struct를 사용해 테스트해 본 결과 GC Alloc은 발생하지 않음
    • 몇몇의 경우는 Stack에 할당이 된다. ex) class의 멤버 변수 타입으로 사용, 인스턴싱된 struct 객체를 objectboxing하는 경우, …
  • Stack에 메모리가 할당된다는 의미는?
    • struct 타입의 변수를 정의할 때 struct 크기만큼 메모리를 Stack에 복사해야 한다는 의미이다
    • 아래 코드의 성능을 측정했을 때 struct의 경우 크기가 작으면 성능이 비슷하게 나오지만 크기가 클수록 복사 비용이 늘어나 성능 하락이 발생한다
    • class의 경우는 포인터 방식의 접근으므로 메모리 복사가 이루어지지 않아 크기와 관계없이 일정한 성능을 유지한다
    • 결과적으로 struct 타입의 크기가 크면 Stack에 메모리를 복사하지 않고 Reference 타입과 같이 포인터 방식으로 접근해 메모리 복사 비용을 낮춰야 한다
    • 2023-08-26-csharp-12-15-38-49.png

테스트 코드

public struct LittleStruct
{
    public int int1;
    public int int2;
    public int int3;
}

public struct BigStruct
{
    public long long1;
    public long long2;
    public long long3;
    public long long4;
    public long long5;
    public long long6;
    public long long7;
    public long long8;
    public long long9;
    public long long10;
    public long long11;
    public long long12;
    public long long13;
}

public class LittleClass
{
    public int int1;
    public int int2;
    public int int3;
}

public class BigClass
{
    public long long1;
    public long long2;
    public long long3;
    public long long4;
    public long long5;
    public long long6;
    public long long7;
    public long long8;
    public long long9;
    public long long10;
    public long long11;
    public long long12;
    public long long13;
}

public class PerformanceTest
{
    private static readonly LittleStruct[] LittleStructArr = new LittleStruct[100];
    private static readonly BigStruct[] BigStructArr = new BigStruct[100];

    public static readonly LittleClass[] LittleClassArr = new LittleClass[100];
    public static readonly BigClass[] BigClassArr = new BigClass[100];

    static PerformanceTest()
    {
        for (int i = 0; i < LittleClassArr.Length; i++)
            LittleClassArr[i] = new LittleClass();

        for (int i = 0; i < BigClassArr.Length; i++)
            BigClassArr[i] = new BigClass();

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    [Benchmark]
    public void NoCopied_LittleStruct()
    {
        int count = 0;
        for (int i = 0; i < LittleStructArr.Length; ++i)
        {
            if (LittleStructArr[i].int1 == 1)
                ++count;
            if (LittleStructArr[i].int2 == 2)
                ++count;
            if (LittleStructArr[i].int3 == 3)
                ++count;
        }
    }

    [Benchmark]
    public void Copied_LittleStruct()
    {
        int count = 0;
        for (int i = 0; i < LittleStructArr.Length; ++i)
        {
            LittleStruct data = LittleStructArr[i];
            if (data.int1 == 1)
                ++count;
            if (data.int2 == 2)
                ++count;
            if (data.int3 == 3)
                ++count;
        }
    }

    [Benchmark]
    public void NoCopied_BigStruct()
    {
        int count = 0;
        for (int i = 0; i < BigStructArr.Length; ++i)
        {
            if (BigStructArr[i].long1 == 1)
                ++count;
            if (BigStructArr[i].long2 == 2)
                ++count;
            if (BigStructArr[i].long3 == 3)
                ++count;
        }
    }

    [Benchmark]
    public void Copied_BigStruct()
    {
        int count = 0;
        for (int i = 0; i < BigStructArr.Length; ++i)
        {
            BigStruct data = BigStructArr[i];
            if (data.long1 == 1)
                ++count;
            if (data.long2 == 2)
                ++count;
            if (data.long3 == 3)
                ++count;
        }
    }

    [Benchmark]
    public void NoCopied_LittleClass()
    {
        int count = 0;
        for (int i = 0; i < LittleClassArr.Length; ++i)
        {
            if (LittleClassArr[i].int1 == 1)
                ++count;
            if (LittleClassArr[i].int2 == 2)
                ++count;
            if (LittleClassArr[i].int3 == 3)
                ++count;
        }
    }

    [Benchmark]
    public void Copied_LittleClass()
    {
        int count = 0;
        for (int i = 0; i < LittleClassArr.Length; ++i)
        {
            LittleClass data = LittleClassArr[i];
            if (data.int1 == 1)
                ++count;
            if (data.int2 == 2)
                ++count;
            if (data.int3 == 3)
                ++count;
        }
    }

    [Benchmark]
    public void NoCopied_BigClass()
    {
        int count = 0;
        for (int i = 0; i < BigClassArr.Length; ++i)
        {
            if (BigClassArr[i].long1 == 1)
                ++count;
            if (BigClassArr[i].long2 == 2)
                ++count;
            if (BigClassArr[i].long3 == 3)
                ++count;
        }
    }

    [Benchmark]
    public void Copied_BigClass()
    {
        int count = 0;
        for (int i = 0; i < BigClassArr.Length; ++i)
        {
            BigClass data = BigClassArr[i];
            if (data.long1 == 1)
                ++count;
            if (data.long2 == 2)
                ++count;
            if (data.long3 == 3)
                ++count;
        }
    }
}


💻 열심히 공부해서 작성 중이니 오류나 틀린 부분이 있을 경우 
  언제든지 댓글 혹은 메일로 알려주시면 감사하겠습니다! 😸

맨 위로 이동하기

CSharp 카테고리 내 다른 글 보러가기

댓글 남기기