SORU
26 EKİM 2011, ÇARŞAMBA


Neden İçeren() Varlık Çerçeve'bu kadar dramatik performansını operatör aşağılamak?

GÜNCELLEME 3: this announcement bu EF6 alfa EF ekibi tarafından giderilmiş 2. Göre

GÜNCELLEME 2: Bu sorunu çözmek için bir öneri yaptım. Bu, go here oy.

Çok basit bir tablo ile bir SQL veritabanı düşünün.

CREATE TABLE Main (Id INT PRIMARY KEY)

Ben 10,000 kayıtları ile bir tablo doldurmak.

WITH Numbers AS
(
  SELECT 1 AS Id
  UNION ALL
  SELECT Id   1 AS Id FROM Numbers WHERE Id <= 10000
)
INSERT Main (Id)
SELECT Id FROM Numbers
OPTION (MAXRECURSION 0)

Ben tablo için EF bir model oluşturmak ve LİNQPad aşağıdaki sorguyu çalıştırın (kullanıyorum "C# İfadeleri LİNQPad dökümü otomatik olarak oluştur) olmaz" modunda.

var rows = 
  Main
  .ToArray();

Yürütme zamanı ~0.07 saniye. Şimdi İçerir operatörü ve yeniden çalıştırın, sorgu ekliyorum.

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  Main
  .Where (a => ids.Contains(a.Id))
  .ToArray();

Bu durumda yürütme zamanı20.14 saniye(288 kez daha yavaş)!

İlk başta T-SQL sorgu için yayılan daha uzun çalıştırmak için aldığını tahmin ettiğim, ve SQL Server Management Studio içine LİNQPad SQL bölmesinden kesme yapıştırma çalıştım.

SET NOCOUNT ON
SET STATISTICS TIME ON
SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Primary] AS [Extent1]
WHERE [Extent1].[Id] IN (1,2,3,4,5,6,7,8,...

Ve sonuç şuydu

SQL Server Execution Times:
  CPU time = 0 ms,  elapsed time = 88 ms.

LİNQPad tahmin ettiğim bir sonraki soruna neden oldu, ancak performans bir konsol uygulaması ben LİNQPad çalıştırın ya aynı.

Yani, sorun bir yerde Varlık Çerçevesi içinde olduğu görülmektedir.

Yanlış bir şey yapıyor buradayım? Bu performansını hızlandırmak için yapabileceğim bir şey var bu yüzden benim kod, zaman açısından kritik bir parçası mı?

Varlık Çerçevesi 4.1 ve Sql Server 2008 R2 kullanıyorum.

GÜNCELLEME 1:

Aşağıdaki tartışma geri alınan veri ayrıştırma iken gecikme EF ilk sorgu yapı oluştu olup olmadığını veya hakkında bazı soruları vardı. Aşağıdaki kodu inceledim bu test için

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  (ObjectQuery<MainRow>)
  Main
  .Where (a => ids.Contains(a.Id));
var sql = rows.ToTraceString();

EF veritabanı karşı yürütülen sorgu oluşturmak için zorlar. Sonuç bu kod ~20 gerekli çalıştırmak için secords, zaman hemen hemen tüm ilk sorgu Bina içinde alınan görünür oldu.

Kurtarmaya CompiledQuery? O kadar hızlı değil ... CompiledQuery parametreleri sorgu geçirilen temel türleri gerektirir (int, string, şamandıra, vb.). Kimliklerinin listesi için kullanabilirim. yani diziler kabul veya IEnumerable, olmayacak.

CEVAP
29 EKİM 2011, CUMARTESİ


GÜNCELLEME: EF6 Sayısız dramatik performans geliştirmeleri içerir.İçerir.

Çoğu zaman sorgu çeviri işleme harcanan haklısın. EF sağlayıcı modeli şu anda doğal madde, bu nedenle ADO.NET sağlayıcıları bakamam bir temsil eden bir ifade yer almamaktadır. Bunun yerine, Sayısız uygulanması.YA da ifadeler bir ağaca çevirir içerir, yani bir C# gibi bu gibi görünüyor:

new []{1, 2, 3, 4}.Contains(i)

... bu şekilde temsil edilebilir DbExpression bir ağaç meydana gelecektir:

((1 = @i) OR (2 = @i)) OR ((3 = @i) OR (4 = @i))

(İfade ağaçları olmak zorunda dengeli çünkü eğer biz tüm ORs üzerinde bir tek uzun omurga orada olacak daha fazla şansı olduğunu ifade ziyaretçi olur hit bir yığın taşması (evet, biz gerçekten mi vurdu bizim test))

Biz daha sonra bu deseni tanır ve SQL üretimi sırasında tümcesi bunu azaltmak için yeteneğine sahip olabilir ADO.NET sağlayıcı, bu gibi bir ağaç değil.

Sayısız için destek ekledik.Bulunduğu EF4, biz düşünülüyordu arzu yapmak zorunda kalmadan tanıtmak için destek ifadelerde sağlayıcı modeli, ve dürüst olmak gerekirse, 10.000 daha fazla öğe sayısını tahmin ettiğimizden müşteriler istiyorsunuz geçirmek için Sayısız.İçerir. O, bu bir sıkıntı olduğunu ve ifadeler Ağaç İşleme şeyler çok pahalı belirli bir senaryoda yapar bunu anlıyorum " dedi.

Bizim geliştiricilerinden biri ile görüştüm ve gelecekte için birinci sınıf destek ekleyerek uygulanması değiştirebiliriz inanıyoruz. Bu bizim bekleme listesi eklenir emin olacağım, ama bunu yapmak istiyorum diğer birçok iyileştirmeler vardır verilen yapacak zaman söz ver.

İçin geçici çözümler iplik zaten önerilen şu: eklemek istiyorum

Geçmesi öğelerin sayısı ile veritabanı gidiş-dönüş sayısını İçerir dengeleyen bir yöntem oluşturmayı düşünün. Benim kendi test örneği için SQL yerel bir örneği karşı yürütülen 100 bilgi işlem elemanları ile sorgu saniyenin 1/60 alır Server fark ettim. Eğer sen-ebilmek yazmak senin sorgu ve infaz 100 Sorgu ile 100 farklı kimlikleri verecekti sana eşdeğer sonuç için sorgu 10.000 elemanları, o halde senin sonuçlarında yaklaşık 1.67 saniye yerine 18 saniye.

Farklı parça boyutları daha iyi sorgu ve veritabanı bağlantısı gecikmesi bağlı olarak çalışması gerekir. Eğer sırası geçti çiftleri varsa, ya da Sayısız.bazı sorgular için, yani Elde iç içe geçmiş durumda kullanılır sonuçlarında yinelenen öğeleri içerir.

Burada kod parçacığı eğer kod parçaları içine girdi dilim için kullanılan çok karmaşık görünüyor. (üzgünüm Orada daha basit yollar elde etmek için aynı şeyi de deniyorum ama bir desen korur akış sırası ve bulamadım bir şey gibi SERİ, yani ben fazla kaçırmışım o kısmı :) ):

Kullanımı:

var list = context.GetMainItems(ids).ToList();

Bağlam ya da depo için yöntem:

public partial class ContainsTestEntities
{
    public IEnumerable<Main> GetMainItems(IEnumerable<int> ids, int chunkSize = 100)
    {
        foreach (var chunk in ids.Chunk(chunkSize))
        {
            var q = this.MainItems.Where(a => chunk.Contains(a.Id));
            foreach (var item in q)
            {
                yield return item;
            }
        }
    }
}

Sayısız dizileri Dilimleme yöntemleri uzantısı:

public static class EnumerableSlicing
{

    private class Status
    {
        public bool EndOfSequence;
    }

    private static IEnumerable<T> TakeOnEnumerator<T>(IEnumerator<T> enumerator, int count, 
        Status status)
    {
        while (--count > 0 && (enumerator.MoveNext() || !(status.EndOfSequence = true)))
        {
            yield return enumerator.Current;
        }
    }

    public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> items, int chunkSize)
    {
        if (chunkSize < 1)
        {
            throw new ArgumentException("Chunks should not be smaller than 1 element");
        }
        var status = new Status { EndOfSequence = false };
        using (var enumerator = items.GetEnumerator())
        {
            while (!status.EndOfSequence)
            {
                yield return TakeOnEnumerator(enumerator, chunkSize, status);
            }
        }
    }
}

Bu yardımcı olur umarım!

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • alex maybury

    alex maybury

    20 Aralık 2007
  • Evan Coury

    Evan Coury

    29 NİSAN 2007
  • Peter Sharp

    Peter Sharp

    11 ŞUBAT 2013