Я использую LINQ для сравнения двух DataSets друг с другом, чтобы создать новые строки и обновить существующие. Я заметил, что полное сравнение длится ~ 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-сервером (на том же сервере, что и эта Windows-служба). Он выступает в качестве источника отчетов, созданных другой службой. Но эти отчеты создаются только один раз в день. Но кроме этого мне было интересно изучать PLINQ, потому что я думал, что это может быть отличным упражнением.
Это занимает 1,5 часа, только если целевая БД пуста и все записи должны быть созданы. Если обе базы данных почти синхронизированы, этот метод занимает всего ~ 1 минуту. В будущем производительность станет более важной, поскольку электронная почта - это только один из нескольких типов контактов (чат + звонки будут превышать 1 млн. Записей). Я думаю, что в любом случае мне понадобится какая-то (LINQ) передача данных.
Если что-то неясно, я обновлю свой ответ соответственно. Заранее спасибо.
Редактировать : Вот результат моих исследований и попыток:
Вопрос: Как "PLINQ" существующий запрос LINQ с объединениями?
Ответ : Обратите внимание, что некоторые операторы LINQ являются двоичными - они принимают два IEnumerable в качестве входных данных. Join является прекрасным примером такого оператора. В этих случаях тип самого левого источника данных определяет, будет ли использоваться 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 Framework и пространства имен Sytem.Threading.Tasks
.
Я понял, что не получу выгоды от распараллеленного Except
, так как запрос достаточно быстр в последовательном режиме (даже с почти одинаковым количеством строк в обеих коллекциях, что приводит к максимальному количеству сравнений, я получил результат менее чем за 30 секунд). Но я добавлю это ради полноты позже.
Поэтому я решил распараллелить for-each
, что так же просто, как с LINQ-Queries, вам просто нужно добавить AsParallel()
в конце.
Но я понял, что мне нужно форсировать параллелизм с 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
(чч: мм: сс.мм):
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
за такое же время?
Ниже приведены рекомендуемые значения: