참고 : https://learn.microsoft.com/ko-kr/dotnet/api/system.linq.enumerable.oftype

 

OfType 

실행 쿼리문 메소드
지연된 실행 없음 OfType

사용예시

필터결과 = 집합.OfType<클래스>()

- 집합내의 아이템들 중 클래스에 해당하는 타입의 아이템들을 뽑아낸다.

 

OfType은 메소드로만 존재하고, 쿼리문은 없습니다.

 

예제 및 활용

기본적인 예

    class Animal
    {

    }
    class Dog : Animal
    {

    }
    class Cat : Animal
    {

    }
    List<Animal> animals = new List<Animal>()
    {
        new Animal(),
        new Dog(),
        new Cat()
    };
    
    // 주인공 OfType
    var res = animals.OfType<Dog>();
    
    
    // 아래는 위 OfType과 동일한 로직을 Where과 일반 문법으로 표기한 겁니다.
    // where절일 때,
    var res = animals.Where(item => item.GetType() == typeof(Dog));

    // 일반적인 문법일 때,
    foreach(var item in animals)
    {
        if(item.GetType() == typeof(Dog))
        {
        }
    }

 

Animal을 상속받은 클래스 중에 Dog 클래스만을 필터링하는 코드입니다.

 

object가 최상위 클래스니까 object List에 여러 클래스를 담아서 원하는 클래스를 뽑아서 쓸 수도 있겠지만,, 그렇게 쓰는 곳이 있을런지 의문이네요.

 

특정 클래스를 상속받아 하나의 리스트에 담는 경우는 흔합니다.  같은 클래스를 상속받아 함수를 클래스별로 다르게 구현하고, list 루프를 돌면서 실행시키는 식으로 많이 쓰잖아요.

예를 들어, 모든 몬스터 (오크, 스켈레톤, 뱀파이어 등)를 이동시키고자 하는데, 다 이동범위가 제각각이라면, 최상이 클래스에 이동 함수를 구현하고, 각 몬스터마다 상속받아 이동범위를 다르게 구현하겠죠.

 이 때 오크들만 뽑아서 잠깐 스톱시키고 싶다! 이럴 때 OfType으로 오크 클래스만 필터링해서 Stop 시킬 수 있겠네요.

 

다만, OfType은 Where절로도 구현이 가능합니다.

성능을 한번 PS에서 비교해보겠습니다.

 

PS. 성능 비교해보기.

1. foreach 와 Where절, OfType  비교하기.

 코드 :

    private void Start()
    {
        for(int i = 0; i < 100000; i++)
        {
            animals.Add(new Animal());
            animals.Add(new Dog());
            animals.Add(new Dog());
            animals.Add(new Cat());
            animals.Add(new Cat());
            animals.Add(new Cat());
        }
        Debug.Log("List count " + animals.Count);
    }

    public void OnClick1()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        foreach (var item in animals)
        {
            if (item.GetType() == typeof(Dog))
            {
            }
        }

        sw.Stop();
        Debug.Log($"foreach Time : {sw.ElapsedMilliseconds}ms");
    }
    public void OnClick2()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();


        var res = animals.Where(item => item.GetType() == typeof(Dog));

        foreach(var item in res)
        {
        }

        sw.Stop();
        Debug.Log($"Where Time : {sw.ElapsedMilliseconds}ms");

    }

    public void OnClick3()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        var res = animals.OfType<Dog>();

        foreach (var item in res)
        {
        }

        sw.Stop();
        Debug.Log($"OfType Time : {sw.ElapsedMilliseconds}ms");
    }

 

결과:

상세 프로파일링 결과:

Foreach:

 Where:

 OfType:

결론:

foeach-if보다 where이 1.5배 정도 느리고, OfType 이 where보다 1.5배 정도 느리네요.

당혹스럽네요... Where로도 할 수 있는 걸 따로 메소드를 뺏길래 더 특화되서 좋은 건가? 했는데, 더 안좋네요?

뭔가 별도의 사용 용도가 있는 걸까요? 모르겠네요.

 

이전 where 절에서 테스트 했을 때는 foreach-if가 20~40배 가량 압도적으로 빨랐는데, 이번엔 1.5배가 나왔네요. 상황에 따라 다르긴 하지만 foreach-if가 Where절보다 빠르단 건 변하지 않았네요

 

(제대로 Type  필터 로직이 돌았는지는 count()를 통해 각각 200000의 결과가 나온건 확인했습니다. 프로파일링에서도 확인 할 수 있습니다.)

 

최종 결론:

 - foreach-if (일반 문법) > Where > OfType 순으로 속도가 빠릅니다. ( OfType이 제일 느리고, 각각 1.5배 정도 이상의 차이가 난다.)

 - OfType 전 안쓸랍니다...

 

 

 

 

테스트 환경

  • cpu - i5-11400F
  • gpu - GeForce RTX 3060
  • Unity 2021.3.13f1
  • VisualStudio 2022
Posted by 검은거북

 

Linq에서 가장 기초가 되는 Where 문부터 시작하겠습니다.

참고 : https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/concepts/linq/filtering-data

 

 

필터링 메소드란?

필터링은 지정된 조건을 충족하는 요소만 포함하도록 결과 집합을 제한하는 작업을 가리킵니다.  라고 공식문서에서 말하네요.

그냥 간단하게 말해서 조건에 만족하는 요소만 추출해낸다.

예를 들어, 키가 180 이상인 사람만 고른다.  과일 중 귤만 고른다.

 

이런 필터링 메소드에 포함되는 Linq 메소드가 Where, OfType 이 있더군요.

Where은 정말 자주 사용하는데, OfType은 처음 알았네요. 사실 Where절이 워낙 범용적이라, Where로도 OfType은 대신할 수 있을거 같습니다만.... 성능면에서는 어떨지는 OfType에서 한 번 테스트해봐야겠네요.

 

Where 절

실행 쿼리문 메소드
지연된 실행 where Where

사용예시

필터결과 = 집합.Where(item => 필터 조건절)

- 집합내의 아이템들을 모두 조건절로 비교해서 부합하는 것만 뽑아내는 겁니다.

 

예제 및 활용

가장 기본적인 예

        char[] chars = new char[] {'a','b','k','q','g'};
        var res = chars.Where(item => item == 'k');

 

chars 배열 중에 k를 찾아줘 라는 구문이죠

 

Where 절을 사용 안하고 foreach문을 쓰면 아래와 같아집니다.

        foreach (var item in chars)
        {
            if (item == 'k')
            {
            }
        }

 

linq가 훨씬 깔끔하죠? 

코드가 복잡해질 수록, 조건자가 복잡해질 수록 더더욱 깔끔해집니다.

 

당연하게도 조건절에는 얼마든지 if문에 넣을 수 있는 것들은 다 들어갈 수 있습니다

        var res = chars.Where(item => item == 'k' && item == 'q');

 

뭐 이런식으로 &&, ||도 얼마든지 사용 가능!

 

하지만! 밑에서도 PS로 설명하겠지만, Where절은 편의성은 올라가지만 성능면에서는 떨어지는 편입니다. 모든 Linq가 그렇겠지만 코드 가독성, 유지보수 측면에서는 쓰기좋지만, 최적화가 중요한 곳에서는 남용하지 않으시길 권장드립니다.

 

 

PS. 성능 비교해보기.

1. Where절과 Foreach - if 를 직접 비교하기.

 코드 :

    public void OnClick1()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        var res = chars.Where(item => item == 'k');

        sw.Stop();
        Debug.Log($"Linq Time : {sw.ElapsedMilliseconds}ms");
    }

    public void OnClick2()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        foreach (var item in chars)
        {
            if (item == 'k')
            {
            }
        }

        sw.Stop();
        Debug.Log($"foreach Time : {sw.ElapsedMilliseconds}ms");
    }

 

결과:

 

상세 프로파일링 결과:

 Linq:

 

 Foreach:

 

결론:

얼핏 보면... Linq가 0ms!? 대박 할 수도 있는데.... 이건 Where절이 실행이 안된겁니다.

이게 Linq 특유의 "지연된 실행" 입니다.

Where절만 써서는 실제로 실행되지 않아요. 담아만 두고, 어딘가에서 Count() 또는 Foreach 등에서 사용을 할 때 비로소 실행이 됩니다.

 

그럼 이번엔 Foreach에 담아서 실행이 되게 해보죠.

 

2. Where절 - Foreach 와 Foreach - if 를 비교하기.

코드 :

    public void OnClick1()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        var res = chars.Where(item => item == 'k');

        foreach (var item in res)
        {
        }

        sw.Stop();
        Debug.Log($"Linq Time : {sw.ElapsedMilliseconds}ms");

    }

    public void OnClick2()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        foreach (var item in chars)
        {
            if (item == 'k')
            {
            }
        }

        sw.Stop();
        Debug.Log($"foreach Time : {sw.ElapsedMilliseconds}ms");
    }

 

결과:

프로파일링 결과:

Linq:

 

Foreach:

 

결론:

Linq가 확 느려졌죠? Foreach에 넣으면서 실제로 Where절이 실행되었기 때문입니다.

같은 foreach로 돌린거니까, if문과 Where절의 비교라고 볼 수 있겠네요.

즉, 지연되었던 실행이 실행되면 Linq의 Where절은 Foreach - if 보다 40배가량 느리다 입니다.

(여러번 테스트해본 결과 평균적으로 40배정도의 데이터가 나왔습니다.)

 

- Count 함수로 Where 절을 실행시켜 테스트 했을 때는 10배가량 느렸던 결과를 보았습니다. 대략 10 ~ 40배 라고 생각해도 꽤 큰 성능 차이죠? 

 

 

최종 결론:

 - Linq의 Where 절은 "지연된 실행"으로 실제로 다른 함수에 의해 실행되기까지 실행되지 않는다.

 - Linq의 Where 절은 같은 로직의 Foreach - if문보다 대략 40배정도 느리다.

 - 편의성은 올라가지만 성능면에서는 떨어지기 때문에 최적화가 중요한 곳에서는 남용하지 않기를 권장드립니다.

  (성능 저하를 잡기위해 프로파일링을 해보면 Linq에서 잡히는 경우가 꽤나 많습니다)

 

 

 

 

테스트 환경

  • cpu - i5-11400F
  • gpu - GeForce RTX 3060
  • Unity 2021.3.13f1
  • VisualStudio 2022
Posted by 검은거북

목차

  • 개요
  •  필터
  • 프로젝션 (변환? 투사?)
    • Select
    • SelectMany
    • Zip
  • 진행중...

 

Linq란 무엇인가?

https://learn.microsoft.com/ko-kr/dotnet/csharp/linq/

라고 공식문서가 말하네요.

뭔가.... 어려운 말 막 쓰는데, 그냥 단순하게,

SQL처럼 쿼리형식으로 데이터 처리 (필터링/ 가공 등) 가 가능하게 해주는 언어 집합이다.

라고 생각하면 편할거 같네요.

 

특징으로는 "지연된 실행"이 있습니다.

결과가 필요한 함수 (Count, Max 등) 나 foreach에 사용하지 않는한 실행이 지연된다는 의미인데, 이걸 간과하고 코딩하면 발적화를 느껴보실 수 있습니다;;

 

사용해보며 느낀 장단점

장점

  • 코드 가독성 향상
  • 코드 재사용성 / 확장성 향상
    • 유지보수에 좋겠죠
    • 저는 게임에서 필터링하는 구간은 대부분 Linq를 애용하긴 합니다.

단점

  • 성능 부하 증가
  • 잘못쓰면 더더 성능 부하 증가

 

참고

공식문서에서는 쿼리 방식과 매서드 방식이 있는데, 성능상에 다른 점은 없지만, 쿼리 방식이 가독성이 좋으니 권장한다고 합니다. 다만 매서드 방식으로만 할 수 있는 함수들이 있다고 하더라고요.

 

개인적으로는 가독성은 주관적인 영역이니 권장하는 이유라 보기 힘들거같고, (실제로 전 매서드가 더 보기 편합니다) 매서드 방식으로만 할 수 있는게 있다면, 매서드 방식을 권장하는게 맞지않나? 싶어요. 음... 전 그래서 향후 포스팅에서는 매서드 방식으로 진행할 예정입니다.

 

 

여담

전 이직하고 처음으로 Linq를 접하고, 어언... 5년정도 쓴 거 같습니다. Linq 사용법 자체가 어렵진 않아서 금방 사용하는 방법은 익혔는데, 사용하다보니 성능상에 문제가 좀 있는 아이더군요. 

단순한 데이터 처리면 별 문제 없겠지만, Linq가 주로 사용되는 곳이 데이터 처리, 즉, 수백개의 데이터를 처리하는데 사용되다보니, 성능 부하로 프로파일링을 해보면 꽤 자주 보이더라고요.

굳이 필요가 없는데, 자주 호출하는 함수에 ToList를 해놨다거나, 너무 남용해서 썼다거나 등등... 그래서 인식이 쓰기 편하고, 보기에는 이쁘지만, 퍼포먼스는 나쁜 아이로 인식이 됐습니다.

그래서 어깨너머로 배워 쓰던 Linq를 제대로 공부해보려고 합니다. 이왕 공부하는거 포스팅으로도 남기고, Linq 백과사전을 함 만들어보자라는 포부로 시작해보려고 합니다.

 

 

 

Posted by 검은거북

블로그 이미지
프로그래밍 공부 요약 및 공부하며 발생한 궁금증과 해결과정을 포스팅합니다.
검은거북

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.5
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

최근에 올라온 글

최근에 달린 댓글

글 보관함