2012-10-15 23 views
13

मैंने अपनी प्रदर्शन समस्या पर बहुत कुछ खोजा और विभिन्न प्रकार की सभी कोशिशों की कोशिश की, लेकिन मुझे लगता है कि यह पर्याप्त तेज़ी से काम करने के लिए प्रतीत नहीं होता है। यहां मेरी सबसे सरल रूप में मेरी समस्या है:कोडफर्स्ट लोडिंग 1 पेरेंट 25 000 बच्चों से जुड़ा हुआ है धीमा

मैं इकाई फ्रेमवर्क 5 का उपयोग कर रहा हूं और जब मैं उस अभिभावक का चयन करता हूं तो मैं अभिभावक के बच्चे के उदाहरणों को आलसी लोड करने में सक्षम होना चाहता हूं, इसलिए मुझे पूरे को खींचने की ज़रूरत नहीं है डेटाबेस। हालांकि मुझे बच्चों को आलसी लोडिंग के साथ प्रदर्शन समस्याएं आ रही हैं। मुझे लगता है कि समस्या माता-पिता और बच्चों के बीच नेविगेशन गुणों का तार है। मैं यह भी सोच रहा हूं कि यह कुछ गलत होना चाहिए क्योंकि मुझे विश्वास है कि यह एक साधारण मामला है।

तो मैंने समस्या को अलग करने के लिए एक आलसी लोड का परीक्षण करने के लिए एक कार्यक्रम प्रस्तुत किया।

यहाँ टेस्ट है:

मैं एक POCO जनक वर्ग और एक बाल POCO वर्ग बनाया। माता-पिता में बच्चे हैं और बच्चे के पास 1 माता-पिता हैं। एसक्यूएल सर्वर डेटाबेस में केवल 1 अभिभावक और उस एकल माता-पिता के लिए 25 000 बच्चे हैं। मैंने इस डेटा को लोड करने के लिए विभिन्न तरीकों का प्रयास किया। जब भी मैं एक ही डीबीकॉन्टेक्स्ट में बच्चों और माता-पिता को लोड करता हूं, तो इसमें काफी समय लगता है। लेकिन अगर मैं उन्हें विभिन्न डीबी कॉन्टैक्स में लोड करता हूं, तो यह वास्तव में तेज़ हो जाता है। हालांकि, मैं उन उदाहरणों को एक ही डीबीकॉन्टेक्स्ट में रखना चाहता हूं।

यहाँ अपने परीक्षण सेटअप और सब कुछ है कि आप इसे दोहराने की जरूरत है:

Pocos:

public class Parent 
{ 
    public int ParentId { get; set; } 

    public string Name { get; set; } 

    public virtual List<Child> Childs { get; set; } 
} 

public class Child 
{ 
    public int ChildId { get; set; } 

    public int ParentId { get; set; } 

    public string Name { get; set; } 

    public virtual Parent Parent { get; set; } 
} 

DbContext:

public class Entities : DbContext 
{ 
    public DbSet<Parent> Parents { get; set; } 

    public DbSet<Child> Childs { get; set; } 
} 

TSQL स्क्रिप्ट बनाने के लिए डाटाबेस ase और डेटा:

USE [master] 
GO 

IF EXISTS(SELECT name FROM sys.databases 
    WHERE name = 'PerformanceParentChild') 
    alter database [PerformanceParentChild] set single_user with rollback immediate 
    DROP DATABASE [PerformanceParentChild] 
GO 

CREATE DATABASE [PerformanceParentChild] 
GO 
USE [PerformanceParentChild] 
GO 
BEGIN TRAN T1; 
SET NOCOUNT ON 

CREATE TABLE [dbo].[Parents] 
(
    [ParentId] [int] CONSTRAINT PK_Parents PRIMARY KEY, 
    [Name] [nvarchar](200) NULL 
) 
GO 

CREATE TABLE [dbo].[Children] 
(
    [ChildId] [int] CONSTRAINT PK_Children PRIMARY KEY, 
    [ParentId] [int] NOT NULL, 
    [Name] [nvarchar](200) NULL 
) 
GO 

INSERT INTO Parents (ParentId, Name) 
VALUES (1, 'Parent') 

DECLARE @nbChildren int; 
DECLARE @childId int; 

SET @nbChildren = 25000; 
SET @childId = 0; 

WHILE @childId < @nbChildren 
BEGIN 
    SET @childId = @childId + 1; 
    INSERT INTO [dbo].[Children] (ChildId, ParentId, Name) 
    VALUES (@childId, 1, 'Child #' + convert(nvarchar(5), @childId)) 
END 

CREATE NONCLUSTERED INDEX [IX_ParentId] ON [dbo].[Children] 
(
    [ParentId] ASC 
) 
GO 

ALTER TABLE [dbo].[Children] ADD CONSTRAINT [FK_Children.Parents_ParentId] FOREIGN KEY([ParentId]) 
REFERENCES [dbo].[Parents] ([ParentId]) 
GO 

COMMIT TRAN T1; 

अनुप्रयोग।config कनेक्शन स्ट्रिंग युक्त

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
    <connectionStrings> 
    <add 
     name="Entities" 
     providerName="System.Data.SqlClient" 
     connectionString="Server=localhost;Database=PerformanceParentChild;Trusted_Connection=true;"/> 
    </connectionStrings> 
</configuration> 

टेस्ट कंसोल वर्ग:

लोड केवल DbSet से माता-पिता:

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Parent> parents; 
     List<Child> children; 

     Entities entities; 
     DateTime before; 
     TimeSpan childrenLoadElapsed; 
     TimeSpan parentLoadElapsed; 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      parents = entities.Parents.ToList(); 
      parentLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load only the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      children = entities.Childs.ToList(); 
      childrenLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load only the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      parents = entities.Parents.ToList(); 
      parentLoadElapsed = DateTime.Now - before; 

      before = DateTime.Now; 
      children = entities.Childs.ToList(); 
      childrenLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds" + 
               ", then load the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      children = entities.Childs.ToList(); 
      childrenLoadElapsed = DateTime.Now - before; 

      before = DateTime.Now; 
      parents = entities.Parents.ToList(); 
      parentLoadElapsed = DateTime.Now - before; 


      System.Diagnostics.Debug.WriteLine("Load the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds" + 
               ", then load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      parents = entities.Parents.ToList(); 
      parentLoadElapsed = DateTime.Now - before; 

      before = DateTime.Now; 
      children = parents[0].Childs; 
      childrenLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds" + 
               ", then load the children from Parent's lazy loaded navigation property:" + childrenLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      parents = entities.Parents.Include(p => p.Childs).ToList(); 
      parentLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load the parent from DbSet and children from include:" + parentLoadElapsed.TotalSeconds + " seconds"); 

     } 

     using (entities = new Entities()) 
     { 
      entities.Configuration.ProxyCreationEnabled = false; 
      entities.Configuration.AutoDetectChangesEnabled = false; 
      entities.Configuration.LazyLoadingEnabled = false; 
      entities.Configuration.ValidateOnSaveEnabled = false; 

      before = DateTime.Now; 
      parents = entities.Parents.Include(p => p.Childs).ToList(); 
      parentLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load the parent from DbSet and children from include:" + parentLoadElapsed.TotalSeconds + " seconds with everything turned off"); 

     } 

    } 
} 

यहाँ उन परीक्षण के परिणाम हैं 0,972 सेकंड

केवल डीबीसेट से बच्चों को लोड करें: 0,714 सेकंड onds

लोड DbSet से माता-पिता: 8,6026 सेकंड

लोड DbSet से बच्चों: 0,6864 सेकंड, तो DbSet से माता-पिता को लोड: 7,5816159 0,001 सेकंड है, तो DbSet से बच्चों को लोड सेकंड

लोड DbSet से माता-पिता: 0 सेकंड, तो अभिभावक की आलसी लोड नेविगेशन संपत्ति से बच्चों को लोड: 8,5644549 सेकंड

लोड DbSet और से बच्चों से माता-पिता शामिल हैं: 8,6428788 सेकंड

लोआ घ DbSet और से बच्चों से माता-पिता शामिल हैं: सब कुछ के साथ 9,1416586 सेकंड बंद

दिया विश्लेषण

जब भी माता-पिता और बच्चों के एक ही DbContext में हैं, यह एक लंबा समय लगता (9 सेकंड) सब कुछ तार करने के लिए। मैंने प्रॉक्सी सृजन से लेकर आलसी लोडिंग तक सबकुछ बंद करने का भी प्रयास किया, लेकिन इसका कोई फायदा नहीं हुआ। क्या कोई मेरी सहायता कर सकता है ?

+0

+1: महान प्रश्न और विश्लेषण! – Slauma

उत्तर

5

मैंने पहले similar question का उत्तर दिया। मेरे पिछले उत्तर में इस मुद्दे का उत्तर देने वाला सिद्धांत है लेकिन आपके विस्तृत प्रश्न के साथ मैं सीधे बता सकता हूं कि समस्या कहां है। सबसे पहले प्रदर्शन प्रोफाइलर के साथ समस्याग्रस्त मामलों में से एक को चलाने देता है। यह DotTrace से परिणाम है, जब पता लगाने के मोड का उपयोग:

enter image description here

फिक्सिंग संबंधों पाश में चलाता है। इसका मतलब यह है 25.000 रिकॉर्ड के लिए आप 25.000 पुनरावृत्तियों है लेकिन इन पुनरावृत्तियों की प्रत्येक आंतरिक CheckIfNavigationPropertyContainsEntityEntityCollection पर कॉल:

internal override bool CheckIfNavigationPropertyContainsEntity(IEntityWrapper wrapper) 
{ 
    if (base.TargetAccessor.HasProperty) 
    { 
     object navigationPropertyValue = base.WrappedOwner.GetNavigationPropertyValue(this); 
     if (navigationPropertyValue != null) 
     { 
      if (!(navigationPropertyValue is IEnumerable)) 
      { 
       throw new EntityException(Strings.ObjectStateEntry_UnableToEnumerateCollection(base.TargetAccessor.PropertyName, base.WrappedOwner.Entity.GetType().FullName)); 
      } 
      foreach (object obj3 in navigationPropertyValue as IEnumerable) 
      { 
       if (object.Equals(obj3, wrapper.Entity)) 
       { 
        return true; 
       } 
      } 
     } 
    } 
    return false; 
} 

भीतरी लूप के पुनरावृत्तियों की संख्या बढ़ती है जैसे आइटम नेविगेशन संपत्ति में जुड़ जाते हैं। गणित मेरे पिछले उत्तर में है - यह अंकगणितीय श्रृंखला है जहां आंतरिक लूप के पुनरावृत्तियों की कुल संख्या 1/2 * (n^2 - n) => n^2 जटिलता है। बाहरी लूप के अंदर आंतरिक लूप के परिणामस्वरूप आपके मामले में 312.487.500 पुनरावृत्तियों के साथ-साथ प्रदर्शन ट्रेसिंग शो भी दिखाते हैं।

मैंने इस समस्या के लिए work item on EF CodePlex बनाया है।

+0

यह एक अच्छा जवाब है, बहुत बहुत धन्यवाद। मैं भी आभारी हूं कि आपने ईएफ कोडप्लेक्स पर एक कार्य आइटम बनाया है। – CurlyFire

5

यह उत्तर नहीं है क्योंकि मेरे पास प्रदर्शन में सुधार करने का कोई समाधान नहीं है, लेकिन टिप्पणी अनुभाग में निम्नलिखित के लिए पर्याप्त स्थान नहीं है। मैं बस कुछ अतिरिक्त परीक्षण और अवलोकन जोड़ना चाहता हूं।

सबसे पहले, मैं लगभग सभी सात परीक्षणों के लिए लगभग आपके मापा समय को पुन: पेश कर सकता हूं। मैंने परीक्षण के लिए ईएफ 4.1 का उपयोग किया है।

कुछ रोचक बातें ध्यान रखें:

  • (उपवास) परीक्षा 2 से मुझे लगता है कि वस्तु भौतिकीकरण (परिवर्तित पंक्तियों और स्तंभों वस्तुओं में डेटाबेस सर्वर से लौटे) धीमी गति से नहीं है निष्कर्ष निकालना होगा।

  • यह भी परिवर्तन ट्रैकिंग के बिना परीक्षण 3 में संस्थाओं लोड करके पुष्टि की है:

    parents = entities.Parents.AsNoTracking().ToList(); 
    // ... 
    children = entities.Childs.AsNoTracking().ToList(); 
    

    इस कोड को चलाता है तेजी से हालांकि 25001 वस्तुओं के रूप में अच्छी तरह से materialized जा करने के लिए (लेकिन नेविगेशन गुण के बीच कोई रिश्ते होगा कायम करना!)।

  • इसके अलावा (तेज) परीक्षण 2 मैं निष्कर्ष निकालूंगा कि परिवर्तन ट्रैकिंग के लिए इकाई स्नैपशॉट बनाना धीमा नहीं है।

  • परीक्षण 3 और 4 में माता-पिता और 25000 बच्चों के बीच संबंध तय हो जाते हैं जब इकाइयों को डेटाबेस से लोड किया जाता है, यानी।ईएफ सभी Child इकाइयों को माता-पिता के Childs संग्रह में जोड़ता है और लोड किए गए माता-पिता को प्रत्येक बच्चे में Parent सेट करता है। जाहिरा तौर पर इस कदम धीमी है, आप पहले से ही अनुमान लगाया है:

    मुझे लगता है कि समस्या नेविगेशन गुण जनक और बच्चों के बीच का तार ऊपर है।

    विशेष रूप से संबंधों के संग्रह पक्ष समस्या लगती है: आप Parent कक्षा में Childs नेविगेशन संपत्ति बाहर टिप्पणी है (संबंध अभी भी एक आवश्यक एक-से-अनेक संबंध तो है) 3 और 4 का परीक्षण करती है तेज़ हैं, हालांकि ईएफ अभी भी सभी 25000 Child इकाइयों के लिए Parent संपत्ति सेट करता है।

    मुझे नहीं पता कि रिलेशनशिप फ़िक्सअप के दौरान नेविगेशन संग्रह भरना इतना धीमा क्यों है। यदि आप इसे निष्क्रिय तरीके से मैन्युअल रूप से अनुकरण करते हैं, तो ...

    entities.Configuration.ProxyCreationEnabled = false; 
    
    children = entities.Childs.AsNoTracking().ToList(); 
    parents = entities.Parents.AsNoTracking().ToList(); 
    
    parents[0].Childs = new List<Child>(); 
    foreach (var c in children) 
    { 
        if (c.ParentId == parents[0].ParentId) 
        { 
         c.Parent = parents[0]; 
         parents[0].Childs.Add(c); 
        } 
    } 
    

    ... यह तेज़ हो जाता है। जाहिर है रिश्ते फिक्सअप आंतरिक रूप से इस सरल तरीके से काम नहीं करता है। (4 सेकंड के आसपास)

    foreach (var c in children) 
    { 
        if (c.ParentId == parents[0].ParentId) 
        { 
         c.Parent = parents[0]; 
         if (!parents[0].Childs.Contains(c)) 
          parents[0].Childs.Add(c); 
        } 
    } 
    

    यह काफी धीमी है: शायद यह जांच की ज़रूरत संग्रह पहले से ही शामिल है अगर बच्चे का परीक्षण किया जा करने के लिए।

वैसे भी, रिलेशनशिप फ़िक्सअप प्रदर्शन बाधा प्रतीत होता है। मुझे नहीं पता कि अगर आपको अपनी संलग्न इकाइयों के बीच परिवर्तन ट्रैकिंग और सही संबंधों की आवश्यकता है तो इसे कैसे सुधारें।

+0

मुझे हाथ देने के लिए धन्यवाद! मैं आपकी टिप्पणियों और अतिरिक्त विश्लेषण के साथ पोस्ट संपादित करूंगा। मैं आपके विश्लेषण को आपके जैसा प्रारूपित भी करूंगा, यह बहुत स्पष्ट है कि एक बिंदु को कम करने के लिए किस परीक्षण का उपयोग किया गया था। – CurlyFire

+0

मैंने कुछ और परीक्षण भी किया, जबकि मैं कुछ मदद की प्रतीक्षा कर रहा था। मैंने एक एनएचबीर्नेट डेटा एक्सेस लेयर बनाया जो एक ही पीओसीओ और टेस्ट कोड का उपयोग करता है (अंतिम 2 को छोड़कर), और प्रत्येक टेस्ट 1 सेकंड के निशान के नीचे चलता है। – CurlyFire

+0

@CurlyFire: क्या NHibernate स्वचालित रूप से संबंधों को ठीक करता है? उदाहरण के लिए, परीक्षण 3 में, एनएच ने सभी भारित इकाइयों के लिए सही वस्तुओं को 'चाइल्ड। माता-पिता' और 'अभिभावक' बच्चे 'सेट किया है? – Slauma

संबंधित मुद्दे