LIST<T>를 열거하는 가장 빠른 방법 | Gérald Barré

CollectionsMarshal.AsSpan()을 이용하면 좀 더 빠르게 열거할 수 있군요. 물론… 열거 중에 수정될 수 있으므로 조심스럽게 사용해야겠습니다.

10개의 좋아요

아니 이런 유익한 정보가…

2개의 좋아요

오오! 굉장하군요.

개인적으로 궁굼해서 테스트를 따라해봤는데요.
디버그일 때에는 놀랍게도 List_ForEach 가 가장 빠르더라구요.
(하지만 릴리즈에서는 가장 느렸…;ㅅ;)

근데 좀 아쉬운 건 AsSpan() 의 인자가 List<T>? 인 게 좀 아쉽습니다.
LINQ 를 이용한 IQuerable 타입에서는 혜택을 보기가 어렵지 않을까 싶네요.
(아님 제가 모르는 방법이 따로 있을까요?ㅅ?)

4개의 좋아요

오늘 @Vincent 님 덕분에 List<T> 구현을 살펴볼 수 있었는데, CollectionsMarshal.AsSpan()이 되려 List<T>를 처리할 수 있었던 이유는 List<T>의 구현이 연속된 배열이기 때문입니다.

CollectionsMarshal.AsSpan()의 구현을 보면 단순히 List<T>의 내부 연속 배열을 Span<T>로 변경했음을 알 수 있습니다.

public static Span<T> AsSpan<T>(List<T>? list)
            => list is null ? default : new Span<T>(list._items, 0, list._size);

그러니 연속된 메모리만 Span<T>의 후보가 된다는 점은 변함이 없네요. ^^;

3개의 좋아요

CollectionsMarshal에는 유용한 GetValueRefOrNullRef()GetValueRefOrAddDefault() 메소드도 있습니다.

C#에서는 사전 키로 값을 설정하거나 변경하고자 할 때 dictionary[key] = value 형태로 사용해야만 합니다. 이 작업은 의외로 몇 단계의 처리가 필요한데, 먼저 키로 탐색을 하고 찾으면 값을 변경하고 없으면 키에 값을 설정합니다.

그런데 다음처럼 동작해야 한다고 칩시다. 찾고자 하는 단어의 개수를 사전에 저장해야 하는데 대상이 100만건이고 그 결과를 반드시 사전의 특정 키 값으로 저장해야 한다고 칩시다.

dictionary["Count"]++;

위의 동작은

dictionary["Count"] = dictionary["Count"] + 1;

이 되고 키 인덱싱을 두번 해야 하고 심지어 이 행위를 100만번 반복해야 하죠.

이전에는 이 코드를 개선할 방법이 제약적이였습니다.

그런데 이제 CollectionsMarshal.GetValueRefOrAddDefault()에 의해 가능해졌습니다.

using System.Runtime.InteropServices;

Dictionary<string, int> dictionary = new();

ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, "Count", out bool exists);

for (var i = 0; i < 1000000; i++)
{
    count++; // dictionary["Count"]의 값을 수정함
}

Console.WriteLine(dictionary["Count"]); // 1000000 출력됨
3개의 좋아요