Metode mana yang berkinerja lebih baik: .Any () vs .Count ()> 0?

578

di System.Linqnamespace, kita sekarang dapat memperluas IEnumerable kita untuk memiliki metode ekstensi Any () dan Count () .

Saya diberitahu baru-baru ini bahwa jika saya ingin memeriksa bahwa koleksi berisi 1 atau lebih item di dalamnya, saya harus menggunakan .Any()metode ekstensi daripada .Count() > 0metode ekstensi karena .Count()metode ekstensi harus beralih melalui semua item.

Kedua, beberapa koleksi memiliki properti (bukan metode ekstensi) yaitu Countatau Length. Apakah lebih baik menggunakan itu, daripada .Any()atau .Count()?

ya / tidak?

Murni
sumber
Lebih baik menggunakan Any () pada Enumerables dan Count on Collections. Jika seseorang merasa menulis '(somecollection.Count> 0)' akan membingungkan atau menyebabkan masalah keterbacaan, lebih baik menulisnya sebagai metode ekstensi beri nama Any (). Lalu semua orang puas. Dari segi kinerja dan juga dari segi keterbacaan. Sehingga semua kode Anda akan memiliki konsistensi dan pengembang individu dalam proyek Anda tidak perlu khawatir tentang memilih Hitung vs Apa pun.
Mahesh Bongani

Jawaban:

709

Jika Anda memulai dengan sesuatu yang memiliki .Lengthatau .Count(seperti ICollection<T>, IList<T>, List<T>, dll) - maka ini akan menjadi pilihan tercepat, karena tidak perlu pergi melalui GetEnumerator()/ MoveNext()/ Dispose()urutan yang dibutuhkan oleh Any()untuk memeriksa non-kosong IEnumerable<T>urutan .

Untuk hanya IEnumerable<T>, maka Any()akan umumnya lebih cepat, karena hanya harus melihat satu iterasi. Namun, perhatikan bahwa implementasi LINQ-to-Objects Count()tidak memeriksa ICollection<T>(menggunakan .Countsebagai optimasi) - jadi jika sumber data dasar Anda secara langsung daftar / koleksi, tidak akan ada perbedaan besar. Jangan tanya saya mengapa itu tidak menggunakan ICollection...

Tentu saja, jika Anda telah menggunakan LINQ untuk memfilternya dll ( Wheredll), Anda akan memiliki urutan berbasis iterator-blok, dan ICollection<T>optimasi ini tidak berguna.

Secara umum dengan IEnumerable<T>: tetap dengan Any();-p

Marc Gravell
sumber
9
Marc: ICollection <T> sebenarnya tidak berasal dari ICollection. Saya terkejut juga, tetapi Reflector tidak berbohong.
Bryan Watts
7
Bukankah implementasi Any () memeriksa antarmuka ICollection dan memeriksa setelah untuk properti Count?
derigel
313
Saya pikir ada alasan lain untuk menggunakan Any () sebagian besar waktu. Ini menandakan niat yang tepat dari pengembang. Jika Anda tidak tertarik untuk mengetahui jumlah item, tetapi hanya jika ada beberapa, maka beberapa koleksi. Setiap () lebih sederhana dan lebih jelas daripada koleksi. Jumlah> 0
TJKjaer
13
@huttelihut - Berapa banyak pengembang yang Anda kenal yang benar-benar bingung dengan pernyataan itu (somecollection.Count > 0)? Apakah semua kode kami sebelum pengenalan metode LINQ .Any () sulit dipahami?
CraigTP
25
@ JLRishe - Saya masih merasa itu someCollection.Count > 0sejelas someCollection.Any()dan memiliki manfaat tambahan dari kinerja yang lebih besar dan tidak memerlukan LINQ. Memang, ini adalah kasus yang sangat sederhana dan konstruksi lainnya menggunakan operator LINQ akan menyampaikan maksud pengembang lebih jelas daripada opsi non-LINQ yang setara.
CraigTP
65

Catatan: Saya menulis jawaban ini ketika Entity Framework 4 aktual. Inti dari jawaban ini adalah untuk tidak masuk ke pengujian sepele .Any()vs .Count()kinerja. Intinya adalah memberi sinyal bahwa EF jauh dari sempurna. Versi yang lebih baru lebih baik ... tetapi jika Anda memiliki bagian dari kode yang lambat dan menggunakan EF, uji dengan TSQL langsung dan bandingkan kinerja daripada mengandalkan asumsi (yang .Any()SELALU lebih cepat daripada .Count() > 0).


Sementara saya setuju dengan jawaban dan komentar yang paling banyak dipilih - terutama pada maksud Anysinyal maksud pengembang lebih baik daripada Count() > 0- Saya memiliki situasi di mana Hitungan lebih cepat berdasarkan urutan besarnya pada SQL Server (EntityFramework 4).

Berikut adalah kueri dengan Anypengecualian batas waktu tersebut (pada ~ 200.000 catatan):

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 versi dieksekusi dalam hitungan milidetik:

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();

Saya perlu menemukan cara untuk melihat apa yang persis dihasilkan oleh kedua LINQ SQL - tapi jelas ada perbedaan kinerja yang sangat besar antara Countdan Anydalam beberapa kasus, dan sayangnya sepertinya Anda tidak bisa hanya bertahan dengan Anysemua kasus.

EDIT: Berikut ini adalah SQL yang dihasilkan. Keindahan seperti yang Anda lihat;)

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], 
[Proyek2]. [Dibuat] AS [Dibuat]
DARI (PILIH [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Dibuat] AS [Dibuat], row_number () LEBIH (ORDER DENGAN [Project2]. [ContactId] ASC) AS [row_number]
    DARI (PILIH 
        [Extent1]. [ContactId] AS [ContactId], 
        [Extent1]. [CompanyId] AS [CompanyId], 
        [Extent1]. [ContactName] AS [ContactName], 
        [Extent1]. [FullName] AS [FullName], 
        [Extent1]. [ContactStatusId] AS [ContactStatusId], 
        [Extent1]. [Dibuat] AS [Dibuat]
        DARI [dbo]. [Kontak] SEBAGAI [Extent1]
        DIMANA ([Extent1]. [CompanyId] = @ p__linq__0) DAN ([Extent1]. [ContactStatusId] <= 3) DAN (BUKAN ADA (SELECT) 
            1 AS [C1]
            DARI [dbo]. [NewsletterLog] AS [Extent2]
            WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) SEBAGAI [Proyek2]
) SEBAGAI [Proyek2]
WHERE [Project2]. [Row_number]> 99
ORDER OLEH [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], 
[Proyek2]. [Dibuat] AS [Dibuat]
DARI (PILIH [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Dibuat] AS [Dibuat], row_number () LEBIH (ORDER DENGAN [Project2]. [ContactId] ASC) AS [row_number]
    DARI (PILIH 
        [Project1]. [ContactId] AS [ContactId], 
        [Project1]. [CompanyId] AS [CompanyId], 
        [Project1]. [ContactName] AS [ContactName], 
        [Project1]. [FullName] AS [FullName], 
        [Project1]. [ContactStatusId] AS [ContactStatusId], 
        [Proyek1]. [Dibuat] AS [Dibuat]
        DARI (PILIH 
            [Extent1]. [ContactId] AS [ContactId], 
            [Extent1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] AS [ContactName], 
            [Extent1]. [FullName] AS [FullName], 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Extent1]. [Created] AS [Created], 
            (PILIH 
                COUNT (1) AS [A1]
                DARI [dbo]. [NewsletterLog] AS [Extent2]
                WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])) SEBAGAI [C1]
            DARI [dbo]. [Kontak] SEBAGAI [Extent1]
        ) SEBAGAI [Proyek1]
        WHERE ([Project1]. [CompanyId] = @ p__linq__0) DAN ([Project1]. [ContactStatusId] <= 3) DAN (0 = [Project1]. [C1])
    ) SEBAGAI [Proyek2]
) SEBAGAI [Proyek2]
WHERE [Project2]. [Row_number]> 99
ORDER OLEH [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Tampaknya murni Dimana dengan EXIS bekerja jauh lebih buruk daripada menghitung Hitung dan kemudian melakukan Mana dengan Hitung == 0.

Beritahu saya jika Anda melihat kesalahan dalam temuan saya. Apa yang bisa diambil dari semua ini terlepas dari diskusi Apa pun vs Hitung adalah bahwa setiap LINQ yang lebih kompleks jauh lebih baik ketika ditulis ulang sebagai Prosedur Tersimpan;).

nikib3ro
sumber
2
Senang melihat beberapa paket Sql Query yang dihasilkan oleh setiap linq-query untuk setiap skenario.
Pure.Krome
43
berdasarkan SQL, yang bisa saya katakan adalah: kedua query terlihat mengerikan. Saya tahu ada alasan saya biasanya menulis TSQL saya sendiri ...
Marc Gravell
! Siapa pun harus memeriksa semua baris seperti Count. Bahwa contoh Anda memberikan hasil yang mengerikan itu agak aneh, dalam kasus terburuk! Apa pun seharusnya hanya sedikit lebih lambat dari Count. Dalam kasus Anda, saya akan mencari cara untuk menyederhanakan seleksi, mungkin membaginya secara bertahap atau menyusun ulang kondisi jika itu mungkin. Tetapi poin Anda bahwa Any lebih baik daripada aturan Count tidak berlaku! Any lebih baik dari Count adalah yang sangat bagus.
Bent
25

Karena ini adalah topik yang agak populer dan jawaban berbeda, saya harus melihat masalah baru.

Pengujian env: EF 6.1.3, SQL Server, catatan 300k

Model tabel :

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

Kode uji:

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();
        }
    }
}

Hasil:

Any () ~ 3ms

Hitung () ~ 230 ms untuk kueri pertama, ~ 400 ms untuk kedua

Catatan:

Untuk kasus saya, EF tidak menghasilkan SQL seperti @Ben yang disebutkan dalam posnya.

kamil-mrzyglod
sumber
4
Untuk perbandingan yang tepat, Anda harus melakukannya Count() > 0. : D
Andrew
1
Andrew, Count ()> 0 tidak akan berjalan berbeda dari Count () dalam tes khusus ini.
CodeMonkeyForHire
11

EDIT: diperbaiki di EF versi 6.1.1. dan jawaban ini tidak lebih aktual

Untuk SQL Server dan EF4-6, Count () melakukan sekitar dua kali lebih cepat daripada Any ().

Ketika Anda menjalankan Table.Any (), itu akan menghasilkan sesuatu seperti ( waspada: jangan sakit otak mencoba memahaminya )

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]

yang membutuhkan 2 pemindaian baris dengan kondisi Anda.

Saya tidak suka menulis Count() > 0karena itu menyembunyikan niat saya. Saya lebih suka menggunakan predikat khusus untuk ini:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}
Ben
sumber
Saya perhatikan ini juga. SQL Any () tidak masuk akal sama sekali. Saya tidak yakin mengapa mereka tidak melakukannya: KASUS KETIKA (ADA (sql)) MAKA 1 LAIN 0 AKHIR. Saya tidak dapat memikirkan alasan mengapa mereka perlu melakukan TIDAK ADA untuk mengembalikan 0.
scott.korin
Ini salah. Anda menemukan paket permintaan yang buruk secara kebetulan. Ini terjadi. Apa pun, hampir selalu, lebih cepat.
usr
Saya memeriksa sql yang dihasilkan pada 6.1.3, mereka memperbaikinya: SELECT CASE SAAT (ADA (SELECT 1 AS [C1] DARI [dbo]. [Tabel Test] SEBAGAI [Extent1] WHERE [Extent1]. [Id]> 1000)) MAKA pemain (1 as bit) ELSE cast (0 as bit) END AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable1]
Ben
6

Tergantung, seberapa besar kumpulan data dan apa persyaratan kinerja Anda?

Jika bukan apa-apa, gunakan bentuk yang paling mudah dibaca, yang bagi saya adalah apa saja, karena bentuknya lebih pendek dan mudah dibaca daripada persamaan.

Timothy Gonzalez
sumber
2

Tentang metode Count () , jika IEnumarable adalah ICollection , maka kami tidak dapat beralih di semua item karena kami dapat mengambil bidang Count ICollection , jika IEnumerable bukan ICollection, kami harus beralih di semua item menggunakan sementara dengan a MoveNext , lihat .NET Framework Code:

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;
}

Referensi: Sumber Referensi Dapat Dihitung

Thiago Coelho
sumber
2

Anda dapat melakukan tes sederhana untuk mengetahui hal ini:

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;

Periksa nilai testCount dan testAny.

Bronks
sumber
1
Ini adalah tes dengan kode Anda untuk properti Hitung vs Apa saja () Hitung kemenangan properti vs Apa saja () dengan + 2x - tautan
Stanislav Prusac
1
Untuk hasil yang lebih baik, Anda bisa melakukan perbandingan ini 1000 kali (atau lebih). Ini membantu untuk meratakan hasil dan menghindari lonjakan acak.
Roman
Ketika Anda menguji seperti metode yang disebutkan di atas, Anda perlu mempertimbangkan lebih banyak faktor, seperti memuat pada basis data / jaringan Anda, merencanakan caching di sisi basis data, dll. Jadi untuk melakukan pengujian yang akurat, Anda harus merancang lingkungan yang terisolasi dan akurat juga.
Vahid Farahmandian
untuk perbandingan yang lebih baik harus Countdiganti dengan metode Count () vs .Any () bukan properti. Anda perlu waktu iterasi.
daremachine
0

Jika Anda menggunakan Entity Framework dan memiliki tabel besar dengan banyak catatan Any () akan jauh lebih cepat. Saya ingat suatu kali saya ingin memeriksa untuk melihat apakah sebuah meja kosong dan ada jutaan baris. Butuh 20-30 detik untuk Count ()> 0 untuk menyelesaikan. Itu instan dengan Any () .

Any () dapat menjadi peningkatan kinerja karena mungkin tidak harus mengulang koleksi untuk mendapatkan sejumlah hal. Itu hanya harus mengenai salah satu dari mereka. Atau, untuk, katakanlah, LINQ-to-Entities, SQL yang dihasilkan akan JIKA ADA (...) daripada SELECT COUNT ... atau bahkan SELECT * ....

Janmejay Kumar
sumber