2012-08-14 27 views
4

Eqatec顯示了每次調用一個方法時都會調用幾千個匿名方法閉包,該方法在我的程序中包含一個簡單的LINQ'Where'語句。僞代碼示例:C#中過度匿名方法閉包(c__DisplayClass1)的原因是什麼?

Class1 
{ 
    //foo and bar are both EF model classes 
    List<foo> aList; // n = 2000 
    List<bar> bList; // n = ~4000 

    void aMethod() 
    { 
     foreach (var item in aList) 
     { 
      Class2.DoSomeWork(item, bList); 
     } 
    } 
} 

Class2 
{ 
    static void DoSomeWork(foo item, List<bar> bList) 
    { 
    var query = bList.where(x => x.prop1 == item.A && x.prop2 = item.B).toList(); // <--- Calls thousands of anonymous method closures each method call. 

    if (query.any()) <--- Calls only 1 anonymous method closure. 
     DoSomethingElse(); 
    } 
} 

我不明白,爲什麼2000調用「DoSomeWork」叫約800萬匿名方法閉包(甚至1導致幾千個)。

作爲一個修復,我簡單地重寫了沒有使用LINQ的語句,從而消除了對閉包的需求,並將性能提高了10倍。

我仍然想知道爲什麼這發生在第一個地方,如果有人有一些他們想分享的理論。

+1

你的意思是「創建8M對象」?還是你的意思是「調用方法8M次」?你發佈的代碼(在清理它之後編譯)***不會創建8M對象 - 它會導致2000,這是我們所期望的(通過'aList')。如果您指的是對象創建,我認爲您在複製/粘貼/清理期間已經更改了內容。我不認爲這種情況現在代表你所看到的。你可以發佈一些更接近原始的東西,理想情況下是a:編譯,而b:顯示問題? – 2012-08-14 07:02:13

回答

4

我認爲8M指的是一個方法在閉包類上執行的次數,而不是創建的閉包實例的數量。首先,讓我們的代碼編譯:

class Class2 
{ 
    public static void DoSomeWork(foo item, List<bar> bList) 
    { 
     var query = bList.Where(x => x.prop1 == item.A && x.prop2 == item.B) 
         .ToList(); 

     if (query.Any()) 
      DoSomethingElse(); 
    } 
    static void DoSomethingElse() { } 
} 
class foo { public int A { get; set; } public int B { get; set; } } 
class bar { public int prop1 { get; set; } public int prop2 { get; set; } } 

現在,我們可以丟棄原來的「// < ---只調用1匿名方法關閉。」評論,因爲實際上沒有匿名方法閉包由.Any()使用 - 它只是檢查列表是否包含內容:不需要關閉。

現在;讓我們手動改寫關閉顯示什麼是發生在編譯器:

class Class2 
{ 
    class ClosureClass 
    { 
     public foo item; // yes I'm a public field 
     public bool Predicate(bar x) 
     { 
      return x.prop1 == item.A && x.prop2 == item.B; 
     } 
    } 
    public static void DoSomeWork(foo item, List<bar> bList) 
    { 
     var ctx = new ClosureClass { item = item }; 
     var query = bList.Where(ctx.Predicate).ToList(); 

     if (query.Any()) { 
      DoSomethingElse(); 
     } 
    } 
    static void DoSomethingElse() { } 
} 

你可以看到,1 ClosureClassDoSomeWork創建,它直接映射到如何只捕獲變量(item)在方法級別的作用域。 謂詞ctx.Predicate)獲得一次(僅),但是對於bList中的每個項目都被調用。所以確實,2000 * 4000是8M調用方法;然而,8M調用方法不一定很慢。

但是!我認爲最大的問題是你正在創建一個新列表來檢查是否存在。你不需要那個。你可以讓你的代碼通過移動更有效的早期Any

if (bList.Any(x => x.prop1 == item.A && x.prop2 == item.B)) { 
    DoSomethingElse(); 
} 

這個現在只調用謂詞足夠的時間,直到找到一個匹配,這是我們應該預見到小於他們都;它也不會不必要地填充列表。

現在; 是的,它將是更有效地做這個手動,即

bool haveMatch = false; 
foreach(var x in bList) { 
    if(x.prop1 == item.A && x.prop2 == item.B) { 
     haveMatch = true; 
     break; 
    } 
} 
if(haveMatch) { 
    DoSomethingElse(); 
} 

但要注意Anyforeach之間的這種變化是關鍵的區別; 至關重要的區別在於我刪除了ToList()和「繼續閱讀,即使您已經找到了匹配項」。 Any(predicate)用法更簡潔,易於閱讀等。它通常不是一個性能問題,我也懷疑它是否在這裏。

+0

最初的'查詢'是一個IEnumerable ,我不知道爲什麼我在這裏將它列爲List。我不知道爲什麼我認爲「DisplayClass」正在創建,而不是簡單訪問。這是一個傑出的答案,有助於澄清我的誤解。謝謝。 – 2012-08-14 08:05:13

3

在線路

var query = bList.where(x => x.prop1 == item.A && x.prop2 = item.B).toList(); 

與具有4000個元素bList,x => x.prop1 == item.A && x.prop2 = item.B將被稱爲4000倍。如果您想要延遲評估.Any(),請刪除.ToList()

相關問題