問題描述

我有兩個數據表:

  • dt:從 CSV 文件填充超過 170 萬行

  • dataStructure.Tables["AccountData"]:從數據庫查詢也是大約一百萬行

我使用以下代碼遍歷並比較每組行的數據。代碼需要 48 小時才能完成。我將應用程序的屬性更改為 x64,以允許它使用更多的 Process Memory 。它現在使用大約 2.5GB 。

我的問題是,有沒有更有效的方法來減少運行時間?

//set is_legal column value for each row
foreach (DataRow row in dt.Rows)
{
    var acctNum = row[0].ToString().Replace(""", "");
    foreach (DataRow queryRow in dataStructure.Tables["AccountData"].Rows)
    {
        var queryAcctNum = queryRow[0].ToString();
        if (acctNum.Equals(queryAcctNum))
        {
            row[12] = "Y";
            Console.WriteLine("Yes count: " + cnt);
        }
        else
        {
            row[12] = "N";
        }
    }
    cnt++;
};

如何填充 dataStructure.Tables["AccountData"]

//Read each row from the table and output the results into the data set
while (readFile.Read())
{
    //Create a row to hold data
    DataRow datarow = dataStructure.Tables["AccountData"].NewRow();

    datarow["AccountNumber"] = readFile.GetString(0).Trim();
    datarow["LegalStatus"] = readFile.GetString(1);

    //add the row to the data table AccountData
    dataStructure.Tables["AccountData"].Rows.Add(datarow);
}

最佳解決思路

你的內部循環似乎是不必要的。為什麼不創建查找:

var knownAccountNumbers = new HashSet<string>(
    dataStructure.Tables["AccountData"].Rows
        .Cast<DataRow>()
        .Select(row => row[0].ToString()));

現在你的循環是簡單的:

foreach (DataRow row in dt.Rows)
{
    var accountNumber = row[0].ToString().Replace(""", "");
    row[12] = knownAccountNumbers.Contains(accountNumber) ? "Y" : "N";
}

我想我記得讀一次,HashSet 的內存使用量是每個條目 12 個字節,條目大小。所以你看的是 12MB + 1,000,000 *(2 * accountNumber.Length) 。所以基本上沒有什麼事情的宏偉計劃。然而,您正在獲得不斷的時間查詢,這對於這種工作應該是一個巨大的好處。


命名時應該更加小心。不要縮寫例如 acctNum – > accountNumber

次佳解決思路

該代碼似乎被破壞,您找不到匹配後不會 break;,所以所有記錄都可能有 row[12] == "N"

你應該真正在 accountNumber 上加入:

var matchingRows =
    from DataRow row in dt.Rows
    let rowKey = row[0].ToString().Replace(""", "")
    join DataRow queryRow in dataStructure.Tables["AccountData"].Rows
    on rowKey equals queryRow[0]
    select row;

foreach(var row in matchingRows)
{
    row[12] = "Y";
}

這樣,您將停止在第一場比賽中進行搜索,並只更新匹配的行。

第三種解決思路

幾點評論:

避免內存密集型數據類型

而不是使用 DataTable for dt,只需從 csv 直接讀取一行一行 (readLine(),然後拆分 (‘,’),這將大大減少內存使用量,而不是一次加載所有 170 萬行,當你一次只使用一個。

排序的數據更快

按帳號對 dataStructure 進行排序。之後,您可以進行二進制搜索以查找帳號,並在您重複它們之後進行休息。這將大大減少你的循環時間。如果您可以在將數據讀入 dataStructure 之前獲取數據庫進行排序,更好。

替代想法

您也可以嘗試將所有 dt 加載到 dataStructure 運行的同一數據庫中的臨時表中,然後使用存儲過程進行更新。數據庫將能夠比 C#中的循環更有效地執行此更新

第四種思路

基於 @Johnbot comment

知道您正在迭代 dataStructure.Tables["AccountData"]的所有記錄,無論您是否找到匹配項。你真的應該 break 出這個循環,如果 acctNum.Equals(queryAcctNum)。這應該加快你的任務很多 (至少如果可以找到數據) 。


另一個可能的增強可能是對錶的行進行排序,並存儲內循環的最後找到的”index” 以將其用作下一次迭代的開始。這將需要將循環從 foreach 更改為 could be faster anyway 的正常 for 循環。

假設 dt 的第一列也被命名為”AccountNumber”,這應該加快進程

    dt.DefaultView.Sort = "AccountNumber";

    var accountDataTable = dataStructure.Tables["AccountData"];
    accountDataTable.DefaultView.Sort  = "AccountNumber";
    int numberOfAccountDataRows = accountDataTable.Rows.Count;
    int currentIndex = 0;

    foreach (DataRow row in dt.Rows)
    {
        var acctNum = row[0].ToString().Replace(""", "");
        for(int i = currentIndex; i < numberOfAccountDataRows; i++) 
        {

            var queryAcctNum = accountDataTable.Rows[i][0].ToString();
            if (acctNum.Equals(queryAcctNum))
            {
                row[12] = "Y";
                currentIndex = i;
                break;
            }

        }
    }

第五種思路

免責聲明:我不在 C#中編碼,所以你可能會失望。如果有人願意將其移植到 C#,請做,這可能會幫助 OP 更多。

比較兩個 data-sets 進行包含/排除可以在流上進行,只要它們被排序。

該算法接近合併排序的合併通過; 在 pseudo-code

left = /*sorted stream 1*/
right = /*sorted stream 2*/
while not left.empty() and not right.empty():
    if left.current() == right.current():
        print "Common item", left.current()
        left.moveToNext()
        right.moveToNext()
        continue
    if left.current() < right.current():
        print "Left specific item", left.current()
        left.moveToNext()
        continue
    if left.current() > right.current():
        print "Right specific item", right.current()
        right.moveToNext()
        continue

while not left.empty()
    print "Left specific item", left.current()
    left.moveToNext()

while not right.empty()
    print "Right specific item", right.current()
    right.moveToNext()

優點:

  • 恆定 memory

  • 快速

缺點:

  • 需要預先排列兩個流

如果您可以輕鬆地按排序順序獲取這兩個流,那麼它可能會獲勝。

如果您必須準備數據集 (排序),那麼對於非常大的數據集 (幾乎不適合內存) 仍然是有利的。

如果兩個數據集都很容易適合內存,而且一個沒有排序,那麼使用另一個解決方案 (在 hash-map 和 look-up 中將較小的一個解壓縮到其中,同時遍歷較大的一個) 。

參考文獻

注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。