簡體   English   中英

為什么我的代碼這么慢?

[英]Why is my code so slow?

我編寫了以下擴展方法以從字典中獲取元素, 如果不存在鍵,則為null

public static TValue ItemOrNull<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
{
    try
    {
        return dict[key];
    }
    catch (KeyNotFoundException ex)
    {
        return default(TValue);
    }
}

我注意到我的程序運行非常緩慢,因此我使用高精度計時器類將問題歸結為此擴展方法。 我連續獲得約100次類似的結果:

DebugTimer.ResetTimer();
    dict1.ItemOrNull(key);
    dict2.ItemOrNull(key);
DebugTimer.StopTimer();

需要大約1億1百萬個滴答聲(在我的處理器上超過0.03秒)。 而更詳細的版本:

DebugTimer.ResetTimer();
    if (dict1.ContainsKey(key))
        y = dict1[key];
    if (dict2.ContainsKey(key))
        z = dict2[key];
DebugTimer.StopTimer();
MessageBox.Show(y.ToString(), z.ToString()) // so the compiler doesn't optimize away y and z

大約需要6,000滴答聲(少於0.000002秒)。

是否有人清楚我的擴展方法版本比冗長版本花費的時間長4個數量級?

不要捕獲用於流控制的異常-不僅是它可能導致性能問題(盡管它們並沒有像大多數人想象的那樣糟糕-就像Eric所說的那樣,大多數人對異常性能的恐懼來自使用調試器)。 它更多地是關於異常的邏輯性質。 就我個人而言, 即使它們本質上是免費的,我也不會以這種方式使用它們

這里有什么不好的事嗎? 用戶要求此密鑰的值以任何方式無效嗎? 絕對不是-該方法的全部目的是提供默認值。 缺少鑰匙並不罕見-因此您應該尋找一種無例外的工作方式。

現在Dictionary<,>已經有一種方法可以讓您在鍵存在的情況下獲取值,並讓您知道是否找到了該鍵: TryGetValue

public static TValue GetValueOrDefault<TKey, TValue>(
    this IDictionary<TKey, TValue> dict,
    TKey key)
{
    TValue ret;
    // We don't care about the return value - we want default(TValue)
    // if it returns false anyway!
    dict.TryGetValue(key, out ret);
    return ret;
}

擴展方法只是編譯為常規的靜態方法調用,因此對它們沒有性能差異。

您可能還想添加一個重載,如果找不到該鍵,則允許用戶表達默認值以返回:

public static TValue GetValueOrDefault<TKey, TValue>(
    this IDictionary<TKey, TValue> dict,
    TKey key, TValue defaultValue)
{
    TValue ret;
    return dict.TryGetValue(key, out value) ? ret : defaultValue;
}

順便說一下,我已經調整了您的方法的名稱以匹配TryGetValue 顯然,您不必遵循它-這只是一個建議。

那是因為您沒有在詳細版本中使用異常。 制作一個使用ContainsKey的擴展方法並進行比較。

澄清一下:異常可能非常緩慢。 這是不依賴它們來控制正常工作流程的原因之一。 它們用於實際上是意料之外的和不需要的情況。

對於FW3.5 +,建議使用TryGetValue

這里有兩個問題:

  1. 您的擴展方法未使用與測試相同的代碼。 如果將其切換為使用相同的代碼(或者更好的是Dictionary.TryGetValue ),則計時會彼此接近。
  2. 我懷疑您是在調試模式下進行計時。 Visual Studio之外的完整版本中進行計時。 在調試模式下(甚至在Release中的VS主機進程中)的計時非常不准確,因為托管進程確實會減慢許多代碼的速度。 擴展方法實際上沒有額外的開銷(它們被編譯為普通的靜態方法調用)-但是,在Visual Studio的調試器中運行時,方法調用通常具有誇大的開銷,因此,這看起來比實際情況要糟糕。

字典是一個哈希表,因此Dictionary.Contains可以快速運行(在此稱為第一步),它不需要花費任何費用,因為它直接檢查內存中與鍵相關的值。 [鍵->哈希,然后檢查內存]

但是嘗試功能的第一個功能已經檢查過! 在返回值鍵之前,對鍵進行哈希處理並檢查內存。 不久,它已經包含Dictionary.Contains的步驟。

您的代碼運行緩慢的原因是“ try”塊(其中包括Dictionary的步驟)。包含缺少的bool值,這會導致陷入catch塊。 這些浪費您的時間。 上面的答案是正確且更詳細的。

您只需將時間值添加到我的Professors語句中,“異常處理是一個代價高昂的過程!請不要在Catch塊中編寫所有邏輯,請嘗試進行編碼,以便進行避免異常的檢查。” 使用異常處理來處理意外的異常,並不是顯而易見的是,當該項不在字典中時,它將崩潰!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM