DataTable.Select и проблема производительности в C#
Я импортирую данные из трех файлов с разделителями табуляции в DataTables, и после этого мне нужно пройти через каждую строку главной таблицы и найти все строки в двух дочерних таблицах. Для каждого массива DataRow[], который я нашел в дочерних таблицах, я должен снова пройти по каждой строке отдельно и проверить значения, основанные на разных параметрах, и в конце мне нужно создать итоговую запись, которая будет объединением главной и двух дочерних таблиц. столбцы таблицы. Теперь я сделал это, и он работает, но проблема в его производительности. Я использую DataTable.Select, чтобы найти все дочерние строки из дочерней таблицы, что, я считаю, делает его очень медленным. Пожалуйста, помните, что ни у одной таблицы нет первичного ключа, так как допустимы повторяющиеся строки. На данный момент у меня есть 1200 строк в основной таблице и около 8000 строк в дочерней таблице, и общее время, необходимое для этого, составляет 8 минут.
Любая идея, как я могу увеличить производительность. заранее спасибо
Код ниже ***************
DataTable rawMasterdt = importMasterFile();
DataTable rawDespdt = importDescriptionFile();
dsHelper = new DataSetHelper();
DataTable distinctdt = new DataTable();
distinctdt = dsHelper.SelectDistinct("DistinctOffers", rawMasterdt, "C1");
if (distinctdt.Rows.Count > 0)
{
int count = 0;
foreach (DataRow offer in distinctdt.Rows)
{
string exp = "C1 = " + "'" + offer[0].ToString() + "'" + "";
DataRow masterRow = rawMasterdt.Select(exp)[0];
count++;
txtBlock1.Text = "Importing Offer " + count.ToString() + " of " + distinctdt.Rows.Count.ToString();
if (masterRow != null )
{
Product newProduct = new Product();
newProduct.Code = masterRow["C4"].ToString();
newProduct.Name = masterRow["C5"].ToString();
// -----
newProduct.Description = getProductDescription(offer[0].ToString(), rawDespdt);
newProduct.Weight = getProductWeight(offer[0].ToString(), rawDespdt);
newProduct.Price = getProductRetailPrice(offer[0].ToString(), rawDespdt);
newProduct.UnitPrice = getProductUnitPrice(offer[0].ToString(), rawDespdt);
// ------- more functions similar to above here
productList.Add(newProduct);
}
}
txtBlock1.Text = "Import Completed";
public string getProductDescription(string offercode, DataTable dsp)
{
string exp = "((C1 = " + "'" + offercode + "')" + " AND ( C6 = 'c' ))";
DataRow[] dRows = dsp.Select( exp);
string descrip = "";
if (dRows.Length > 0)
{
for (int i = 0; i < dRows.Length - 1; i++)
{
descrip = descrip + " " + dRows[i]["C12"];
}
}
return descrip;
}
6 ответов
.Net 4.5 и проблема все еще там.
Вот результаты простого теста, в котором DataTable.Select и различные реализации словаря сравниваются по времени ЦП (результаты в миллисекундах).
#Rows Table.Select Hashtable[] SortedList[] Dictionary[]
1000 43,31 0,01 0,06 0,00
6000 291,73 0,07 0,13 0,01
11000 604,79 0,04 0,16 0,02
16000 914,04 0,05 0,19 0,02
21000 1279,67 0,05 0,19 0,02
26000 1501,90 0,05 0,17 0,02
31000 1738,31 0,07 0,20 0,03
Проблема:
Метод DataTable.Select внутренне создает экземпляр класса "System.Data.Select", а этот класс "Select" создает индексы на основе полей (столбцов), указанных в запросе. Класс Select повторно использует созданные им индексы, но реализация DataTable не использует экземпляр класса Select повторно, поэтому индексы создаются заново каждый раз, когда вызывается DataTable.Select. (Такое поведение можно наблюдать декомпиляцией System.Data).
Решение:
Предположим, следующий запрос
DataRow[] rows = data.Select("COL1 = 'VAL1' AND (COL2 = 'VAL2' OR COL2 IS NULL)");
Вместо этого создайте и заполните словарь с ключами, соответствующими различным комбинациям значений значений столбцов, используемых в качестве фильтра. (Эта относительно дорогая операция должна быть выполнена только один раз, и экземпляр словаря должен быть использован повторно)
Dictionary<string, List<DataRow>> di = new Dictionary<string, List<DataRow>>();
foreach (DataRow dr in data.Rows)
{
string key = (dr["COL1"] == DBNull.Value ? "<NULL>" : dr["COL1"]) + "//" + (dr["COL2"] == DBNull.Value ? "<NULL>" : dr["COL2"]);
if (di.ContainsKey(key))
{
di[key].Add(dr);
}
else
{
di.Add(key, new List<DataRow>());
di[key].Add(dr);
}
}
Запросить словарь (может потребоваться несколько запросов), чтобы отфильтровать строки и объединить результаты в список
string key1 = "VAL1//VAL2";
string key2 = "VAL1//<NULL>";
List<DataRow>() results = new List<DataRow>();
if (di.ContainsKey(key1))
{
results.AddRange(di[key1]);
}
if (di.ContainsKey(key2))
{
results.AddRange(di[key2]);
}
Я знаю, что это старый вопрос, и код, лежащий в основе этой проблемы, возможно, изменился, но я недавно столкнулся (и получил некоторое понимание) с этой самой проблемой.
Для тех, кто придет позже... вот что я нашел.
Производительность DataTable.Select(condition)
достаточно чувствителен к природе и структуре "состояния", которое вы предоставляете. Для меня это похоже на ошибку (куда я мог бы сообщить об этом в Microsoft?), Но это может быть просто причудой.
Я написал набор тестов для демонстрации проблемы, которая структурирована следующим образом:
Определите таблицу данных с помощью нескольких простых столбцов, например:
var dataTable = new DataTable ();
var idCol = dataTable.Columns.Add ("Id", typeof(Int32));
dataTable.Columns.Add ("Код", typeof(строка));
dataTable.Columns.Add ("Имя", typeof(строка));
dataTable.Columns.Add ("FormationDate", typeof(DateTime));
dataTable.Columns.Add ("Income", typeof(Decimal));
dataTable.Columns.Add ("ChildCount", typeof(Int32));
dataTable.Columns.Add ("Foreign", typeof(Boolean));
dataTable.PrimaryKey = new DataColumn [1] {idCol};Заполните таблицу 40000 записями, каждая с уникальным полем "Код".
- Выполните пакет "selects" (каждый с разными параметрами) по данным, используя два одинаковых, но по-разному отформатированных запроса и записи, и сравните общее время, затраченное каждым из двух форматов.
Вы получаете замечательные результаты. Тестирование, например, два следующих условия бок о бок:
Q1: [Код] = 'XX'
Q2: ([Код] = 'XX')
[Я делаю несколько вызовов Select, используя два вышеупомянутых запроса, на каждой итерации я заменяю XX действительным кодом, который существует в таблице данных]. Результат?
Сравнение времени для 320 поисков с 40000 записями: общее время поиска 180 мсек без скобок, общее время поиска 6871 мсек поиска с скобками
Да - в 38 раз медленнее, если у вас есть дополнительные скобки, окружающие условие. Есть и другие сценарии, которые реагируют по-разному.
Например, [Code] = '{searchCode}' OR 1=0
против ([Code] = '{searchCode}' OR 1=0)
выполнить аналогичное (медленное) время выполнения, но:
[Code] = '{searchCode}' AND 1=1
против ([Code] = '{searchCode}' AND 1=1)
снова показывает версию без скобок, чтобы быть ближе к 40 раз быстрее.
Я не исследовал все сценарии, но кажется, что введение скобок - либо избыточно вокруг простой проверки сравнения, либо, как требуется для указания приоритета подвыражения - или наличие "ИЛИ" значительно замедляет запрос.
Я мог бы предположить, что проблема вызвана тем, как datatable анализирует условие, которое вы используете, и как он создает и использует внутренние индексы... но я не буду.
Вы можете ускорить его с помощью словаря. Например:
if (distinctdt.Rows.Count > 0)
{
// build index of C1 values to speed inner loop
Dictionary<string, DataRow> masterIndex = new Dictionary<string, DataRow>();
foreach (DataRow row in rawMasterdt.Rows)
masterIndex[row["C1"].ToString()] = row;
int count = 0;
foreach (DataRow offer in distinctdt.Rows)
{
Тогда на месте
string exp = "C1 = " + "'" + offer[0].ToString() + "'" + "";
DataRow masterRow = rawMasterdt.Select(exp)[0];
Вы бы сделали это
DataRow masterRow;
if (masterIndex.ContainsKey(offer[0].ToString())
masterRow = masterIndex[offer[0].ToString()];
else
masterRow = null;
Если вы создаете DataRelation между родительским и дочерним DataTables, вы можете искать дочерние строки, вызывая DataRow.GetChildRows (DataRelation) в родительской строке (соответственно DataRow.GetChildRelName в случае типизированных DataSets). Поиск будет применять поиск TreeMap, и производительность должна быть хорошей даже при большом количестве дочерних строк.
В случае, если вам нужно искать строки на основе других критериев, отличных от внешних ключей DataRelation, я рекомендую использовать DataView.Sort / DataView.FindRows () вместо DataTable.Select (), как только вам придется запросить данные больше чем один раз. DataView.FindRows () будет основан на поиске TreeMap (O (log (N)), где DataTable.Select () должен сканировать все строки (O (N)). Эта статья содержит более подробную информацию: http://arnosoftwaredev.blogspot.com/2011/02/when-datatableselect-is-slow-use.html
Вы пробежали через профилировщик? Это должно быть первым шагом. Во всяком случае, это может помочь:
Читайте основной текстовый файл в память построчно. Поместите основную запись в словарь в качестве ключа. Добавьте его в набор данных (1 проход через мастера).
Прочитайте дочерний текстовый файл построчно, добавьте это как значение для соответствующей основной записи в словарь, созданный выше
Теперь у вас есть все в словаре в памяти, только делает 1 проход через каждый файл. Сделайте последний проход по словарю / потомкам, обработайте каждый столбец и выполните последние вычисления.
DataTables могут иметь отношения с другими DataTables в DataSet. См. http://msdn.microsoft.com/en-us/library/ay82azad%28VS.71%29.aspx для небольшого обсуждения и в качестве отправной точки для просмотра. У меня не так много опыта их использования, но, насколько я понимаю, они будут делать то, что вы хотите (при условии, что ваши таблицы в подходящем формате). Я бы предположил, что они имеют большую эффективность, чем ручной процесс, но я могу ошибаться. Возможно, стоит посмотреть, работают ли они на вас, и провести сравнительный анализ, чтобы увидеть, являются ли они улучшением или нет...