둘 이상의 항목이 이미 발견 된 경우에도 Enumerable.Single ()이 모든 요소를 반복하는 이유는 무엇입니까?
애플리케이션 중 하나를 약력 링 할 때 Enumerable.Single(source, predicate)
컬렉션 시작 부분에 술어와 일치하는 항목이 두 개 이상있는 존재 컬렉션을 호출하는 일부 코드에서 신비한 속도를 발견 했습니다.
조사에 따르면 의 구현은Enumerable.Single()
다음과 가변 합니다.
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
TSource result = default(TSource);
long count = 0;
// Note how this always iterates through ALL the elements:
foreach (TSource element in source) {
if (predicate(element)) {
result = element;
checked { count++; }
}
}
switch (count) {
case 0: throw Error.NoMatch();
case 1: return result;
}
throw Error.MoreThanOneMatch();
}
이 구현은 둘 이상의 요소가 이미 술어와 일치하더라도 시퀀스의 모든 요소를 반복합니다.
다음 구현은 동일한 결과를 생성합니다.
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
TSource result = default(TSource);
long count = 0;
foreach (TSource element in source) {
if (predicate(element)) {
if (count == 1) // Exit loop immediately if more than one match found.
throw Error.MoreThanOneMatch();
result = element;
count++; // "checked" is no longer needed.
}
}
if (count == 0)
throw Error.NoMatch();
return result;
}
실제 구현이 명백한 최적화를 사용하지 않는 이유를 아는 사람이 없는가? 내가 놓친 유전자 재조합? (나는 명백한 최적화가 간과 될 상상할 수 있고, 어떤 구체적인 이유가 있어야합니다.)
(참고 :이 질문이 의견 인 답변을 끌어낼 수 있다고 생각합니다. 모든 요소를 끌어낼 수 있습니다. 모든 요소를 반복하는 구체적인 이유를 제공하는 답변을 원합니다. 실제로 "디자이너가 적절한 최적화가 필요하다고 생각하지 않습니다"이라고 답한 ,이 질문은 답할 수 삭제해야 할 것입니다 ...)
비교 Single()
를 위해 사용되지 않는 구현을 사용 했습니다 .
public static TSource Single<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null) {
switch (list.Count) {
case 0: throw Error.NoElements();
case 1: return list[0];
}
}
else {
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (!e.MoveNext()) throw Error.NoElements();
TSource result = e.Current;
if (!e.MoveNext()) return result;
}
}
throw Error.MoreThanOneElement();
}
이 경우 .NET Framework에 대한 최적화를 추가하기 위해 노력했습니다
IList
.
당신 만이 그렇게 생각하는 것 같지는 않았습니다. .NET 코어 구현 에 최적화 된 버전이 :
using (IEnumerator<TSource> e = source.GetEnumerator())
{
while (e.MoveNext())
{
TSource result = e.Current;
if (predicate(result))
{
while (e.MoveNext())
{
if (predicate(e.Current))
{
throw Error.MoreThanOneMatch();
}
}
return result;
}
}
}
따라서 귀하의 질문에 답하기 위해 : 개발자가이 사용 사례를 최적화하는 것에 대해 생각하지 않는 것 외에는 '좋은'이유가없는 것 같습니다.
이제 코드는 다음과 같습니다.
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
throw Error.ArgumentNull(nameof(source));
}
if (predicate == null)
{
throw Error.ArgumentNull(nameof(predicate));
}
using (IEnumerator<TSource> e = source.GetEnumerator())
{
while (e.MoveNext())
{
TSource result = e.Current;
if (predicate(result))
{
while (e.MoveNext())
{
if (predicate(e.Current))
{
throw Error.MoreThanOneMatch();
}
}
return result;
}
}
}
throw Error.NoMatch();
}
가능한 경우 코드는 대상이인지 여부를 확인하여 IList<T>
반복을 피할 수 있습니다.
public static TSource Single<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw Error.ArgumentNull(nameof(source));
}
if (source is IList<TSource> list)
{
switch (list.Count)
{
case 0:
throw Error.NoElements();
case 1:
return list[0];
}
}
else
{
using (IEnumerator<TSource> e = source.GetEnumerator())
{
if (!e.MoveNext())
{
throw Error.NoElements();
}
TSource result = e.Current;
if (!e.MoveNext())
{
return result;
}
}
}
throw Error.MoreThanOneElement();
}
최신 정보
git blame 출력을 확인하면 반복 최적화가 2016 년에 다시 적용되었음을 알 수 있습니다!
IList<>
최적화는 아마도 코어 2.1 최적화의 일환으로, 1 년 전 하였다
다른 답변에서 지적했듯이 최적화가 적용되었지만 술어 함수에 사이드가 없다는 것을 보장 할 방법이 없다는 사실을 원래 생각하면서 그렇게 한 가설을 제기하고 싶습니다. 효과.
그런 행동이 실제로 사용 / 유용한 경우가 있을지는 모르겠지만 명심해야 할 고려 사항입니다.
'ProgramingTip' 카테고리의 다른 글
For 루프에서 포크 () (0) | 2020.11.25 |
---|---|
Azure DevOps, YAML 릴리스 파이프 라인? (0) | 2020.11.25 |
JWT를 localStorage 또는 쿠키에 저장해야합니까? (0) | 2020.11.25 |
어두운면으로 가라는 압박을 받고 (0) | 2020.11.25 |
로그 파일 대신 데이터베이스에 기록 (0) | 2020.11.25 |