Подсчет строк в файле CSV, который соответствует строке базы данных, каждая из которых имеет миллион записей

У меня есть два DataTables:

  • dt: заполняется из файла CSV с более чем 1,7 миллионами строк
  • dataStructure.Tables["AccountData"]: заполняется из запроса базы данных также примерно миллионом строк

Я использую следующий код для итерации и сравнения данных из каждого набора строк. Для завершения кода требуется более 48 часов. Я изменил свойства приложения на x64, чтобы он мог использовать больше памяти процесса. Теперь он использует примерно 2,5 ГБ.

Мой вопрос: есть ли более эффективный способ сделать это, чтобы уменьшить время выполнения?

//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);
}
30 голосов | спросил HappyCoding 16 FebruaryEurope/MoscowbTue, 16 Feb 2016 17:50:46 +0300000000pmTue, 16 Feb 2016 17:50:46 +030016 2016, 17:50:46

6 ответов


54

Ваш внутренний цикл кажется ненужным. Почему бы не создать поиск:

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.

ответил RobH 16 FebruaryEurope/MoscowbTue, 16 Feb 2016 18:15:54 +0300000000pmTue, 16 Feb 2016 18:15:54 +030016 2016, 18:15:54
14

Код выглядит сломанным, вы не break; ing после поиска совпадения, поэтому все записи, вероятно, имеют строку 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";
}

Таким образом вы прекратите поиск в первом совпадении и обновите только соответствующие строки.

ответил Johnbot 16 FebruaryEurope/MoscowbTue, 16 Feb 2016 18:15:38 +0300000000pmTue, 16 Feb 2016 18:15:38 +030016 2016, 18:15:38
8

Несколько комментариев:

Избегать использования данных с интенсивной памятью

Вместо использования DataTable для dt, просто прочитайте из csv напрямую, по одной строке за раз (readLine (), а затем разделите (','). Это значительно сократит использование вашей памяти, а не загрузит все 1,7 миллиона строк одновременно, когда вы используете только один за раз.

Отсортированные данные быстрее

Сортировка dataStructure по номеру учетной записи. После этого вы можете выполнить двоичный поиск, чтобы найти номер (ы) учетной записи и перерыв после того, как вы выполнили итерацию по нему /им. Это значительно сократит время цикла. Если вы можете получить базу данных для сортировки данных, прежде чем читать ее в dataStructure, еще лучше.

Альтернативная идея

Вы также можете попробовать загрузить все dt во временную таблицу в той же базе данных, с которой была запущена dataStructure, а затем использовала хранимую процедуру для обновления. База данных сможет сделать это обновление намного эффективнее, чем цикл в C # может

ответил Zack 16 FebruaryEurope/MoscowbTue, 16 Feb 2016 18:20:47 +0300000000pmTue, 16 Feb 2016 18:20:47 +030016 2016, 18:20:47
5

На основе @Johnbot

Правильно знаю, что вы повторяете все записи dataStructure.Tables["AccountData"] независимо от того, найдено ли вы совпадение. Вы действительно должны break выйти из этого цикла, если acctNum.Equals(queryAcctNum). Это должно значительно ускорить вашу задачу (по крайней мере, если данные могут быть найдены).


Другим возможным улучшением может быть сортировка строк таблиц и сохранение последнего найденного «индекса» внутреннего цикла для использования его в качестве начала следующей итерации. Это должно было бы изменить циклы из foreach в обычный цикл 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;
            }

        }
    }
ответил Heslacher 16 FebruaryEurope/MoscowbTue, 16 Feb 2016 18:15:27 +0300000000pmTue, 16 Feb 2016 18:15:27 +030016 2016, 18:15:27
3

Отказ от ответственности: я не кодирую на C #, поэтому вы можете быть разочарованы. Если кто-то захочет передать это на C #, пожалуйста, сделайте это, возможно, это поможет OP больше.

Сравнение двух наборов данных для включения /исключения может быть выполнено в потоках при условии, что они отсортированы.

Алгоритм близок к проходу слияния Merge Sort; в псевдокоде

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()

Преимущества:

  • постоянная память
  • быстро

Недостатки:

  • требует, чтобы оба потока были отсортированы заранее

Если вы можете легко получить обе потоки в отсортированном порядке, вероятно, выиграют руки.

Если вам нужно подготовить набор данных (отсортировать его), то он по-прежнему выгоден для очень больших наборов данных (которые едва вписываются в память).

Если оба набора данных легко вписываются в память, а один не сортируется, используйте другое решение (потяните меньший в хэш-карте и посмотрите на него, итерации по более крупному).

ответил Matthieu M. 17 FebruaryEurope/MoscowbWed, 17 Feb 2016 16:24:04 +0300000000pmWed, 17 Feb 2016 16:24:04 +030016 2016, 16:24:04
0

Спасибо за ответ @ RobH, а также за его оптимизацию кода на микроуровне, например:

  • использовать String Constant
  • in String.Replace использовать тип данных char вместо строкового типа данных
  • knownAccountNumbers было добавлено при извлечении себя

var knownAccountNumbers = new HashSet<string>();

//// If AccountData DataTable used only for condition then 
//// Delete Code Related to AccountData DataTable and Use knownAccountNumbers HashSet alone.
DataTable accountDt = dataStructure.Tables["AccountData"];

while (readFile.Read())
{
    string accountNumber = readFile.GetString(0).Trim();

    knownAccountNumbers.Add(accountNumber);

    DataRow dr = accountDt.NewRow();

    dr[AppConst.AccountNumber] = accountNumber;
    dr[AppConst.LegalStatus] = readFile.GetString(1);

    accountDt.Rows.Add(dr);
}

/* ----------------------------------------------------------------------------------------------- */

foreach (DataRow row in dt.Rows)
{
    //// For single char string replace use data type as char instead of string.
    //// Gives more perfromance
    //// var accountNumber = row[0].ToString().Replace(AppConst.BackSlashChar, AppConst.EmptyChar);
    var accountNumber = row[0].ToString().Replace(AppConst.BackSlash, string.Empty);

    //// Use Constant String will not create unnecessary memory allocation.
    row[12] = knownAccountNumbers.Contains(accountNumber) ? AppConst.Yes : AppConst.No;
}

/*------------------------------------------------------------------------------------------------ */

public static class AppConst
{
    public const string Yes      = "Y";
    public const string No       = "N";
    //// public const char   BackSlashChar = '\"';
    //// public const char   EmptyChar = '\0';
    public const string BackSlash = "\"";

    public const string AccountNumber = "AccountNumber";
    public const string LegalStatus   = "LegalStatus";
}
ответил Thulasiram 5 J000000Wednesday17 2017, 10:34:38

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132