매우 작은 메서드의 주입

예를 들어, 정렬 알고리즘을 제공하는 라이브러리가 있다 칩시다.
비교 메서드도 주입할 수 있도록 되어 있습니다.(델리게이트라 칩시다)
근데, 비교 메서드가 매우 짧기 때문에 그 실행시간에 비해 메서드 호출 비용이 큽니다.
(델리게이트/인터페이스/virtual 메서드 등은 JIT 인라이닝이 안되기 때문)

이럴 때, 해답이 뭔가요?

  1. 라이브러리의 내용을 직접 구현한다
  2. 다른 언어(C++)를 쓴다
  3. 기타…
1개의 좋아요

앗 답을 찾아가는것같습니다 정리 되면 올리겠습니다

1개의 좋아요

sealed 키워드가 원래 그런 용도는 아니지만,
sealed 클래스가 지역적인 스코프 내 지역변수에 생성자로 할당되고 사용되는 경우에는 인라이닝이 되는 것 같습니다. 외부 참조가 없으면 변경 가능성이 없으므로 인터페이스 대신 해당 대상으로 대체할 수 있고, sealed로 선언되어 가상호출이 필요하지 않음이 명백할 때는 그 대상이 가상호출될 필요가 없으므로 비가상 메서드로 취급되는 것 같습니다.
이걸로 작은 메서드도 주입해서 패널티 없이 사용 가능할 것 같습니다.

2개의 좋아요

오 좋은 내용 같은데 혹시 시간이 되신다면 예제 코드를 좀 첨부해서 설명 부탁 드려도 될까요?
이해가 될랑 말랑 ㅋㅋㅋㅋ

sealed로 상속 관련 제약 사항 등을 체크하는 로직을 전부 생략해서 그런 게 아닐까 소소한 짐작을 해봅니다.
++
잠깐 구글링 하다가 나온 결과중에
이런 블로그 글이 있네요.

음, (이 글에 따르면) 막 쓰기에는 생각할 여지가 좀 있지 않을까 싶긴 하네요.

1개의 좋아요

SealedTest.cs

namespace SealedTest
{
    public interface IMyInterface
    {
        void ITest(int[] ints, int i);
    }
    internal class MyInterface : IMyInterface
    {

        public void ITest(int[] ints, int i)
        {
            if (i < ints.Length)
            {
                ints[i] = i;
            }
        }
    }
    internal sealed class MySealedInterface : IMyInterface
    {
        public void ITest(int[] ints, int i)
        {
            if (i < ints.Length)
            {
                ints[i] = i;
            }
        }
    }

    public static class CallTests
    {
        public static IMyInterface g_myInterface = new MySealedInterface();
        public static void GIArgCall<T>(T t, int[] ints, int i) where T : IMyInterface
        {
            t.ITest(ints, i);
        }
        public static void IArgCall(IMyInterface myInterface, int[] ints, int i)
        {
            myInterface.ITest(ints, i);
        }

        public static void ConcCall(int[] ints, int i)
        {
            if (i < ints.Length)
            {
                ints[i] = i;
            }
        }
    }
    public sealed class G<T> where T : IMyInterface
    {
        public static T? t;
        public static void Test(int[] ints, int i)
        {
            t?.ITest(ints, i);
        }
    }
}

Program.cs

// See https://aka.ms/new-console-template for more information
using SealedTest;

Console.WriteLine("Hello, World!\n\n");

Random random = new Random();
//int sel = random.Next(2);

const int arraySize = 50000;
const int repeat = 2023;
int[] arrayConcreteStatic = new int[arraySize];
int[] arrayGArg = new int[arraySize];
int[] arrayIArg = new int[arraySize];
int[] arraySealedIArg = new int[arraySize];
int[] arraySealedI = new int[arraySize];




var interfaceArg = CostTester.Test(
    () =>
    {
        var inter = new MyInterface();
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < arraySize; i++)
            {
                //GInterface.Test2(GInterface.myinterface, arrayIArg, i);
                CallTests.IArgCall(inter, arrayIArg, i);
            }
        }
    }
);



var genericArg = CostTester.Test(
    () =>
    {
        var inter = new MySealedInterface();
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < arraySize; i++)
            {
                //GInterface.Test2(GInterface.myinterface, arrayIArg, i);
                CallTests.GIArgCall(inter, arrayGArg, i);
            }
        }
    }
);

var sealedInterface = CostTester.Test(
    () =>
    {
        var inter = CallTests.g_myInterface;
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < arraySize; i++)
            {
                //GInterface.Test2(GInterface.myinterface, arrayIArg, i);
                CallTests.IArgCall(inter, arraySealedI, i);
            }
        }
    }
);

var sealedInterfaceArg = CostTester.Test(
    () =>
    {
        var inter = new MySealedInterface();
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < arraySize; i++)
            {
                //GInterface.Test2(GInterface.myinterface, arrayIArg, i);
                CallTests.IArgCall(inter, arraySealedIArg, i);
            }
        }
    }
);

var concStatic = CostTester.Test(
    () =>
    {
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < arraySize; i++)
            {

                CallTests.ConcCall(arrayConcreteStatic, i);
            }
        }
    }
);

Console.WriteLine("static 직접 호출: " + "테스트값:" + arrayConcreteStatic[random.Next(arrayConcreteStatic.Length)] + ", 소요 시간:" + concStatic.ms);
Console.WriteLine("제네릭 인자 호출: " + "테스트값:" + arrayGArg[random.Next(arrayGArg.Length)] + ", 소요 시간:" + genericArg.ms);
Console.WriteLine("인터페이스 인자 호출: " + "테스트값:" + arrayIArg[random.Next(arrayIArg.Length)] + ", 소요 시간:" + interfaceArg.ms);
Console.WriteLine("Sealed 인터페이스 호출: " + "테스트값:" + arraySealedI[random.Next(arraySealedI.Length)] + ", 소요 시간:" + sealedInterface.ms);
Console.WriteLine("Sealed 인터페이스 인자 호출: " + "테스트값:" + arraySealedIArg[random.Next(arraySealedIArg.Length)] + ", 소요 시간:" + sealedInterfaceArg.ms);

결과
static 직접 호출: 테스트값:45933, 소요 시간:92
제네릭 인자 호출: 테스트값:22953, 소요 시간:396
인터페이스 인자 호출: 테스트값:33132, 소요 시간:404
Sealed 인터페이스 호출: 테스트값:38922, 소요 시간:378
Sealed 인터페이스 인자 호출: 테스트값:14512, 소요 시간:100

보시면, Sealed 클래스임에도 지역적으로 생성한 인자를 넣어주지 않으면 인라이닝이 안되고…
지역에서 생성한 인자를 넣어줬어도 Sealed가 아니면 인라이닝이 안됩니다.
하지만 Sealed 클래스가 런타임에 구체화된 상태로 인자로 주입될 경우, 인라이닝 되어 직접 호출과 같은 성능을 갖게 되는 것 같습니다.

3개의 좋아요

정성태님이 분석(?)해 주셨네요

사실상 인라인 처리가 아닌 sealed클래스의 특수 성향으로 인한 속도 개선이란 것을


닷넷: 2132. C# - sealed 클래스의 메서드를 callback 호출했을 때 인라인 처리가 될까요? (sysnet.pe.kr)

3개의 좋아요