.net - 더 나은 성능을내는 메소드:.Any()vCount()>0?




linq performance .net-3.5 extension-methods (8)

System.Linq 네임 스페이스에서 이제 IEnumerableAny()Count() 확장 메서드를 추가 할 수 있습니다 .

나는 최근 컬렉션에 하나 이상의 아이템이 포함되어 있는지 확인하고 싶다면 .Any() 확장 메소드 .Any() 사용하기 때문에 .Count() > 0 확장 메소드 대신 .Any() 확장 메소드를 사용해야한다. 모든 항목을 반복해야합니다.

둘째, 일부 콜렉션에는 Count 또는 Length 특성 (확장 메소드가 아님)이 있습니다. .Any() 또는 .Count() 대신에 이들을 사용하는 것이 더 좋을까요?

참으로 / 응?


Answers

데이터 세트가 얼마나 큰지, 실적 요구 사항은 무엇입니까?

거대한 것은 아무것도 아니지만, 가장 독해 가능한 형식을 사용하십시오. 이것은 나 자신을위한 것으로, 방정식 이라기보다 짧고 읽기 쉽기 때문입니다.


음, .Count() 확장 메서드는 .Count 속성을 사용하지 않지만 단순한 컬렉션에 대해서는 .Count() 메서드를 사용하지 않지만 필터링 조건이있는 LINQ 문의 끝 부분에서는 사용한다고 가정합니다. 등

그 맥락에서 .Any().Count() > 0 보다 빠릅니다.


이것은 오히려 인기있는 주제이고 답변이 다르므로 나는 문제에 대해 신선한 시각을 가져야했습니다.

테스트 환경 : EF 6.1.3, SQL Server, 300k 레코드

표 모델 :

class TestTable
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }
}

테스트 코드 :

class Program
{
    static void Main()
    {
        using (var context = new TestContext())
        {
            context.Database.Log = Console.WriteLine;

            context.TestTables.Where(x => x.Surname.Contains("Surname")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname")).Count(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Count(x => x.Id > 1000);

            Console.ReadLine();
        }
    }
}

결과 :

임의 () ~ 3ms

첫 번째 쿼리의 경우 Count () ~ 230ms, 두 번째 쿼리의 경우 ~ 400ms

비고 :

필자의 경우 EF는 @Ben과 같은 SQL을 생성하지 않았다.


이 문제를 파악하기위한 간단한 테스트를 할 수 있습니다.

var query = //make any query here
var timeCount = new Stopwatch();
timeCount.Start();
if (query.Count > 0)
{
}
timeCount.Stop();
var testCount = timeCount.Elapsed;

var timeAny = new Stopwatch();
timeAny.Start();
if (query.Any())
{
}
timeAny.Stop();
var testAny = timeAny.Elapsed;

testCount 및 testAny의 값을 확인하십시오.


참고 : Entity Framework 4가 실제 일 때이 답변을 작성했습니다. 이 대답의 요점은 사소한 일이 아니었다. 모든 .Any() 대. .Count() 성능 테스트. 요점은 EF가 완벽하지 않다는 신호이다. 최신 버전은 더 좋지만 코드가 느리고 EF를 사용하는 경우 직접 TSQL로 테스트하고 가정에 의존하지 않고 성능을 비교하면 ( .Any().Count() > 0 보다 항상 빠름) .

가장 중요한 답변 및 의견에 동의하는 동안 - 특히 신호 개발자Count() > 0 보다 더 나은 의도 를 보이는 점에 대해 - 저는 Count가 SQL Server (EntityFramework 4)에서 더 빠르다고 생각했습니다.

다음은이 시간 초과 예외 (~ 200.000 개 레코드)의 쿼리입니다.

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && !a.NewsletterLogs.Any(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr)
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

밀리 초 단위로 실행되는 Count 버전 :

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && a.NewsletterLogs.Count(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr) == 0
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

정확한 LINQ가 생성되는 SQL을 찾는 방법을 찾아야합니다. 그러나 CountAny 사이의 성능 차이가 큰 경우가 있습니다. 불행히도 모든 경우에 Any 를 사용할 수는없는 것 같습니다.

편집 : 여기에 SQL이 생성됩니다. 보시다시피 아름다움;)

ANY :

exec sp_executesql N'SELECT TOP (1) 
[Project2].[ContactId] AS [ContactId], 
[Project2].[CompanyId] AS [CompanyId], 
[Project2].[ContactName] AS [ContactName], 
[Project2].[FullName] AS [FullName], 
[Project2].[ContactStatusId] AS [ContactStatusId], 
[Project2].[Created] AS [Created]
FROM ( SELECT [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created], row_number() OVER (ORDER BY [Project2].[ContactId] ASC) AS [row_number]
    FROM ( SELECT 
        [Extent1].[ContactId] AS [ContactId], 
        [Extent1].[CompanyId] AS [CompanyId], 
        [Extent1].[ContactName] AS [ContactName], 
        [Extent1].[FullName] AS [FullName], 
        [Extent1].[ContactStatusId] AS [ContactStatusId], 
        [Extent1].[Created] AS [Created]
        FROM [dbo].[Contact] AS [Extent1]
        WHERE ([Extent1].[CompanyId] = @p__linq__0) AND ([Extent1].[ContactStatusId] <= 3) AND ( NOT EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[NewsletterLog] AS [Extent2]
            WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])
        ))
    )  AS [Project2]
)  AS [Project2]
WHERE [Project2].[row_number] > 99
ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4

COUNT :

exec sp_executesql N'SELECT TOP (1) 
[Project2].[ContactId] AS [ContactId], 
[Project2].[CompanyId] AS [CompanyId], 
[Project2].[ContactName] AS [ContactName], 
[Project2].[FullName] AS [FullName], 
[Project2].[ContactStatusId] AS [ContactStatusId], 
[Project2].[Created] AS [Created]
FROM ( SELECT [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created], row_number() OVER (ORDER BY [Project2].[ContactId] ASC) AS [row_number]
    FROM ( SELECT 
        [Project1].[ContactId] AS [ContactId], 
        [Project1].[CompanyId] AS [CompanyId], 
        [Project1].[ContactName] AS [ContactName], 
        [Project1].[FullName] AS [FullName], 
        [Project1].[ContactStatusId] AS [ContactStatusId], 
        [Project1].[Created] AS [Created]
        FROM ( SELECT 
            [Extent1].[ContactId] AS [ContactId], 
            [Extent1].[CompanyId] AS [CompanyId], 
            [Extent1].[ContactName] AS [ContactName], 
            [Extent1].[FullName] AS [FullName], 
            [Extent1].[ContactStatusId] AS [ContactStatusId], 
            [Extent1].[Created] AS [Created], 
            (SELECT 
                COUNT(1) AS [A1]
                FROM [dbo].[NewsletterLog] AS [Extent2]
                WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])) AS [C1]
            FROM [dbo].[Contact] AS [Extent1]
        )  AS [Project1]
        WHERE ([Project1].[CompanyId] = @p__linq__0) AND ([Project1].[ContactStatusId] <= 3) AND (0 = [Project1].[C1])
    )  AS [Project2]
)  AS [Project2]
WHERE [Project2].[row_number] > 99
ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4

순수한 Where With EXISTS는 Count 계산보다 더 나쁜 작업을하고 Count with == 0을 사용합니다.

네가 내 연구 결과에 약간의 오류가 있다면 알려주지. Any versus Count 논의와 상관없이이 모든 것에서 벗어날 수있는 것은 더 복잡한 LINQ는 저장 프로 시저로 재 작성 될 때 더 효율적이라는 것입니다.


IEnumarableICollection 인 경우 Count () 메서드에 대해 ICollectionCount 필드를 검색 할 수 있으므로 모든 항목을 반복 할 수 없습니다. IEnumerableICollection 이 아닌 경우 잠시 동안 모든 항목에 대해 반복해야합니다. MoveNext , .NET Framework 코드 살펴보기 :

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) 
        throw Error.ArgumentNull("source");

    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) 
        return collectionoft.Count;

    ICollection collection = source as ICollection;
    if (collection != null) 
        return collection.Count;

    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        checked
        {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

참조 : 참조 소스 Enumerable


편집 : EF 버전 6.1.1 수정되었습니다. 이 대답은 더 이상 실제가 아닙니다.

SQL Server 및 EF4-6의 경우 Count ()는 Any ()보다 약 2 배 빠릅니다.

Table.Any ()를 실행하면 ( 경고 : 이해하려고하는 두뇌를 해치지 않습니다. )

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent1]
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent2]
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

그것은 당신의 상태와 2 행의 스캔이 필요합니다.

내 의도를 숨기기 때문에 Count() > 0 을 쓰는 것을 좋아하지 않습니다. 나는 이것을 위해 사용자 정의 술어를 선호한다.

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}

다음은 로마 숫자에 대한 to-and-from입니다. 자주 사용하지는 않지만 편리 할 수 ​​있습니다. 용법:

if ("IV".IsValidRomanNumeral())
{
   // Do useful stuff with the number 4.
}

Console.WriteLine("MMMDCCCLXXXVIII".ParseRomanNumeral());
Console.WriteLine(3888.ToRomanNumeralString());

출처 :

    public static class RomanNumeralExtensions
    {
        private const int NumberOfRomanNumeralMaps = 13;

        private static readonly Dictionary<string, int> romanNumerals =
            new Dictionary<string, int>(NumberOfRomanNumeralMaps)
            {
                { "M", 1000 }, 
                { "CM", 900 }, 
                { "D", 500 }, 
                { "CD", 400 }, 
                { "C", 100 }, 
                { "XC", 90 }, 
                { "L", 50 }, 
                { "XL", 40 }, 
                { "X", 10 }, 
                { "IX", 9 }, 
                { "V", 5 }, 
                { "IV", 4 }, 
                { "I", 1 }
            };

        private static readonly Regex validRomanNumeral = new Regex(
            "^(?i:(?=[MDCLXVI])((M{0,3})((C[DM])|(D?C{0,3}))"
            + "?((X[LC])|(L?XX{0,2})|L)?((I[VX])|(V?(II{0,2}))|V)?))$", 
            RegexOptions.Compiled);

        public static bool IsValidRomanNumeral(this string value)
        {
            return validRomanNumeral.IsMatch(value);
        }

        public static int ParseRomanNumeral(this string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            value = value.ToUpperInvariant().Trim();

            var length = value.Length;

            if ((length == 0) || !value.IsValidRomanNumeral())
            {
                throw new ArgumentException("Empty or invalid Roman numeral string.", "value");
            }

            var total = 0;
            var i = length;

            while (i > 0)
            {
                var digit = romanNumerals[value[--i].ToString()];

                if (i > 0)
                {
                    var previousDigit = romanNumerals[value[i - 1].ToString()];

                    if (previousDigit < digit)
                    {
                        digit -= previousDigit;
                        i--;
                    }
                }

                total += digit;
            }

            return total;
        }

        public static string ToRomanNumeralString(this int value)
        {
            const int MinValue = 1;
            const int MaxValue = 3999;

            if ((value < MinValue) || (value > MaxValue))
            {
                throw new ArgumentOutOfRangeException("value", value, "Argument out of Roman numeral range.");
            }

            const int MaxRomanNumeralLength = 15;
            var sb = new StringBuilder(MaxRomanNumeralLength);

            foreach (var pair in romanNumerals)
            {
                while (value / pair.Value > 0)
                {
                    sb.Append(pair.Key);
                    value -= pair.Value;
                }
            }

            return sb.ToString();
        }
    }






.net linq performance .net-3.5 extension-methods