Я использую LINQ для сравнения двух DataSet друг с другом для создания новых строк и обновления Я заметил, что полное сравнение длится ~ 1,5 часа, и только одно из двух ядер занято (диспетчер задач составляет 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% ЦП, я предполагаю, что если бы я распараллелил запросы, это значительно увеличило бы производительность, потому что второе ядро почти бездействует. для каждого
также может быть хорошим местом для оптимизации, поскольку билеты не связаны друг с другом. Поэтому я предполагаю, что мог бы работать в цикле с несколькими потоками и параллельно создавать / обновлять записи. Но как это сделать с помощью PLINQ?
Боковое примечание : Как я уже упоминал в комментариях, производительность пока не является для меня ключевым фактором, поскольку единственная цель сервера - синхронизировать базу данных MySQL (на другом сервере) с MS SQL- Сервер (на том же сервере, что и этот Windows-Сервис). Он действует как источник отчетов, созданных другой службой. Но эти отчеты создаются только один раз в день. Но помимо этого мне было интересно изучить PLINQ, потому что я подумал, что это может быть отличным упражнением. Упомянутые 1,5 часа требуются только в том случае, если база данных назначения пуста и все записи должны быть созданы. Если обе базы данных почти синхронизированы, этот метод займет всего ~ 1 минуту.В будущем производительность станет более важной, поскольку электронная почта является только одним из нескольких типов контактов (чат + звонки будут превышать 1 миллион записей). Я думаю, что тогда мне все равно понадобится какой-нибудь (LINQ) пейджинг данных.
Если что-то неясно, я соответствующим образом обновлю свой ответ. Заранее спасибо.
Изменить : Вот результат моих исследований и попыток:
Вопрос: Как «PLINQ» существующий запрос LINQ с объединениями?
Ответ : Обратите внимание, что некоторые операторы LINQ являются бинарными - они принимают два объекта IEnumerable в качестве входных данных. Join - прекрасный пример такого оператора. В этих случаях тип крайнего левого источника данных определяет, используется ли LINQ или PLINQ. Таким образом, вам нужно вызвать AsParallel только для первого источника данных, чтобы ваш запрос выполнялся параллельно:
IEnumerable 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
.
Я понял, что не получу выгоды от распараллеливания За исключением
, поскольку запрос выполняется достаточно быстро в последовательном режиме (даже с почти одинаковым количеством строк в обеих коллекциях, что приводит к максимальному количеству сравнений , Я получил результат менее чем за 30 секунд).Но для полноты картины добавлю позже.
Поэтому я решил распараллелить для каждого
, что так же просто, как с LINQ-запросами, вам просто нужно добавить AsParallel ()
в конце. {{1} } Но я понял, что мне нужно усилить параллелизм с помощью 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
по сравнению с последовательным режимом:
для каждого
] с 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
Может кто-нибудь объяснить мне этот результат? Имеется ли доступ для записи к базе данных в для каждого
, отвечающего за аналогичное время?
Ниже приведены рекомендуемые материалы для чтения: