Я использую LINQ для сравнения двух наборов данных друг с другом для создания новых строк и обновления существующих. Я заметил, что полное сравнение длится ~ 1,5 часа, и только один из двух ядер занят (Task-Manager - 50-52% использования ЦП). Я должен признать, что я совершенно не знаком с параллельным LINQ, но я предполагаю, что он может значительно повысить производительность.
Итак, мой вопрос: как и что следует распараллелить?
Это оригинальные запросы (сведенные к основному):
'check for new data
Dim srcUnique = From row In src.Email_Total
Select Ticket_ID = row.ticket_id, Interaction = row.interaction, ModifiedAt = row.modified_time
Dim destUnique = From row In dest.ContactDetail
Where row.ContactRow.fiContactType = emailContactType.idContactType
Select row.ContactRow.Ticket_ID, row.Interaction, row.ModifiedAt
'get all emails(contactdetails) that are in source but not in destination
Dim diffRows = srcUnique.Except(destUnique).ToList
'get all new emails(according to ticket_id) for calculating contact columns
Dim newRowsTickets = (From row In src.Email_Total
Join d In diffRows
On row.ticket_id Equals d.Ticket_ID _
And row.interaction Equals d.Interaction _
And row.modified_time Equals d.ModifiedAt
Group row By Ticket_ID = row.ticket_id Into NewTicketRows = Group).ToList
For Each ticket In newRowsTickets
Dim contact = dest.Contact.FindByTicket_IDfiContactType(ticket.Ticket_ID, emailContactType.idContactType)
If contact Is Nothing Then
' Create new Contact with many sub-queries on this ticket(omitted) ****'
Dim newContact = Me.dest.Contact.NewContactRow
dest.Contact.AddContactRow(newContact)
contact = newContact
Else
' Update Contact with many sub-queries on this ticket(omitted) '
End If
daContact.Update(dest.Contact)
' Add new ContactDetail-Rows from this Ticket(this is the counterpart of the src.Email_Total-Rows, details omitted) '
For Each newRow In ticket.NewTicketRows
Dim newContactDetail = dest.ContactDetail.NewContactDetailRow
newContactDetail.ContactRow = contact
dest.ContactDetail.AddContactDetailRow(newContactDetail)
Next
daContactDetails.Update(dest.ContactDetail)
Next
Примечание: daContact
и daContactDetails
являются SqlDataAdapters
, source
и dest
являются DataSets
и Contact
и ContactDetail
являются DataTables
, где каждый ContactDetail принадлежит контакту.
Даже если бы оба ядра не использовали 100% -ный процессор, я предполагаю, что он значительно увеличит производительность, если я буду распараллелить запросы, потому что второе ядро почти бездействует. for each
также может быть хорошим местом для оптимизации, поскольку билеты не связаны друг с другом. Поэтому я предполагаю, что я мог бы цикл с несколькими потоками и создавать/обновлять записи параллельно. Но как это сделать с PLINQ?
Боковое примечание. Как я уже упоминал в комментариях, производительность пока не является ключевым фактором для меня, поскольку целью только сервера является синхронизация базы данных MySQL (на другой сервер) с MS SQL-Server (на том же сервере, что и эта служба Windows). Он действует как источник отчетов, которые генерируются другой службой. Но эти отчеты генерируются только один раз в день. Но кроме этого я был заинтересован в изучении PLINQ, потому что я думал, что это может быть отличным упражнением. Он занимает указанные 1,5 часа, только если БД назначения пуста и все записи должны быть созданы. Если обе базы данных почти синхронизированы, этот метод занимает всего ~ 1 минуту. В будущем производительность станет более важной, поскольку электронная почта - это только один из нескольких типов контактов (чат + звонки превышают 1 милли.счет). Я думаю, что в любом случае мне понадобится какой-то (LINQ) Data-Paging.
Если что-то неясно, я соответственно обновлю ответ. Спасибо заранее.
Изменить. Вот результат моих исследований и попыток:
Вопрос: Как "PLINQ" существующий запрос LINQ с объединениями?
Answer: Обратите внимание, что некоторые операторы LINQ являются двоичными: в качестве входных данных они принимают два IEnumerables. Присоединение - прекрасный пример такого оператора. В этих случаях тип самого левого источника данных определяет, используется ли LINQ или PLINQ. Таким образом, вам нужно только вызвать AsParallel в первом источнике данных для параллельного выполнения запроса:
IEnumerable<T> leftData = ..., rightData = ...;
var q = from x in leftData.AsParallel()
join y in rightData on x.a == y.b
select f(x, y);
Но если я изменю свой запрос следующим образом (обратите внимание на AsParallel
):
Dim newRowsTickets = (From row In src.Email_Total.AsParallel()
Join d In diffRows
On row.ticket_id Equals d.Ticket_ID _
And row.interaction Equals d.Interaction _
And row.modified_time Equals d.ModifiedAt
Group row By Ticket_ID = row.ticket_id Into NewTicketRows = Group).ToList
Компилятор будет жаловаться, что мне нужно добавить AsParallel
в нужный источник данных. Таким образом, это, похоже, проблема VB.NET или отсутствие документации (статья с 2007 года). Я предполагаю, что последнее, потому что (помимо этой рекомендуемой) статьи также говорится, что вам нужно добавить System.Concurrency.dll
вручную, но на самом деле это часть платформы .NET 4.0 и в пространстве имен Sytem.Threading.Tasks
.
Я понял, что не буду получать прибыль от распараллеленного Except
, так как запрос выполняется достаточно быстро в последовательном режиме (даже при почти одинаковом числе строк в обоих сборках, что приводит к максимальному количеству сравнений, я получил результат менее 30 секунд). Но я добавлю это ради полноты позже.
Итак, я решил распараллелить for-each
то, что так же просто, как с LINQ-Queries, вам просто нужно добавить AsParallel()
в конец.
Но я понял, что мне нужно заставить parallelism с WithExecutionMode(ParallelExecutionMode.ForceParallelism)
, иначе .NET решает использовать только одно ядро для этого цикла. Я также хотел сказать .NET, что я хочу использовать как можно больше потоков, но не более 8: WithDegreeOfParallelism(8).
Теперь оба ядра работают одновременно, но использование процессора остается на 54%.
Итак, это версия PLINQ:
Dim diffRows = srcUnique.AsParallel.Except(destUnique.AsParallel).ToList
Dim newRowsTickets = (From row In src.Email_Total.AsParallel()
Join d In diffRows.AsParallel()
On row.ticket_id Equals d.Ticket_ID _
And row.interaction Equals d.Interaction _
And row.modified_time Equals d.ModifiedAt
Group row By Ticket_ID = row.ticket_id Into NewTicketRows = Group).ToList
For Each ticket In newRowsTickets.
AsParallel().
WithDegreeOfParallelism(8).
WithExecutionMode(ParallelExecutionMode.ForceParallelism)
' blah,blah ... '
'add new ContactDetails for this Ticket(only new rows)
For Each newRow In ticket.NewTicketRows.
AsParallel().
WithExecutionMode(ParallelExecutionMode.Default)
' blah,blah ... '
Next
daContactDetails.Update(dest.ContactDetail)
Next
К сожалению, я не вижу преимуществ в производительности от использования AsParallel
по сравнению с последовательным режимом:
for each
с AsParallel
(hh: mm: ss.mm):
09/29/2011 18:54:36: Contacts/ContactDetails created or modified. Duration: 01:21:34.40
И без:
09/29/2011 16:02:55: Contacts/ContactDetails created or modified. Duration: 01:21:24.50
Может кто-нибудь объяснить мне этот результат? Доступна ли запись базы данных в for each
за аналогичное время?
Ниже приведены рекомендуемые показания: