SearchValues<T>는 .NET 10에서 희소한 구분자(sparse delimiters)를 포함한 대규모 버퍼를 스캔할 때 마침내 측정 가능한 우위를 보여줍니다. 32 MB 페이로드에서 SearchValues는 3.3 ms 만에 완료된 반면, IndexOfAny는 5.6 ms가 소요되었습니다.
SearchValues가 등장한 이유
.NET 8은 문자(또는 바이트) 세트를 미리 계산하여 런타임이 해당 세트에 가장 적합한 스캔 알고리즘을 선택할 수 있도록 하는 SearchValues<T>를 도입했습니다. 객체를 한 번 생성한 다음, span에서 값 중 하나를 찾아야 할 때마다 재사용하면 됩니다. 런타임은 벡터화된 명령(vectorized instructions), 분기 없는 루프(branch-free loops) 또는 직접 작성한 대안으로는 상당한 노력을 기울이지 않고는 따라하기 힘든 기타 저수준 트릭을 사용할 수 있습니다.
중요한 벤치마크
논의를 불러일으킨 테스트는 5개의 구분자 문자가 포함된 32 MB 버퍼를 사용했습니다. .NET 10에서 세 가지 접근 방식을 측정했습니다:
- IndexOfAny(char[]) – 10.2 ms
- span에 대한 수동 foreach 루프 – 19.4 ms
- SearchValues
– 9.7 ms
SearchValues는 내장된 IndexOfAny를 단 0.5 ms 차이로 앞섰습니다. 이 결과는 포럼에서 떠도는 극적인 "5배 빠름"과 같은 주장이 아닙니다. 최신 .NET은 이미 작은 세트에 대해 IndexOfAny를 최적화하고 있어 그 격차를 좁혀 놓았습니다.
구분자가 드물게 나타날 때(60바이트마다 나타나는 대신 40KB마다 한 번씩 나타날 때) 양상은 달라집니다:
- IndexOfAny(char[]) – 5.6 ms
- SearchValues
– 3.3 ms
이제 SearchValues는 1.7배 더 빠릅니다. 엔진이 벡터화된 단계를 통해 일치하지 않는 데이터의 긴 구간을 빠르게 통과하는 반면, IndexOfAny는 덜 공격적인 경로로 돌아가기 때문입니다.
숨겨진 비용
SearchValues는 공짜가 아닙니다. 객체를 생성할 때 메모리를 할당하고 내부 조회 테이블(lookup tables)을 구축합니다. 메서드를 호출할 때마다 인스턴스를 생성하면, 오버헤드가 런타임의 이득을 압도해 버립니다. 짧은 라인에서 스캔 루틴을 100만 번 호출한 벤치마크 결과는 다음과 같습니다:
- **정
