Подтвердить что ты не робот

Как выполнить PLINQ существующий запрос LINQ с помощью Joins?

Я использую 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 за аналогичное время?


Ниже приведены рекомендуемые показания:

4b9b3361

Ответ 1

Есть 3 вопроса, заслуживающих дальнейшего изучения,

  • Не использовать .toList(). Возможно, я ошибаюсь, но я думаю, что использование .ToList этот способ не позволит компилятору оптимизировать запрос, если возможна дальнейшая оптимизация.
  • Используйте свою собственную операцию фильтрации для сравнения данных с обоих destionations. Это может дать вам лучшую производительность.
  • Посмотрите, можете ли вы использовать LinqDataview, чтобы обеспечить лучшую производительность.

    Я не думаю, что вы получите преимущество PLinq при вставке. Посмотрите этот ответ для более подробной информации.

Надеюсь, что это поможет. Пожалуйста, спросите, требуется ли вам разъяснение по любому из вышеуказанных пунктов.