13

LINQ を使用して 2 つの DataSet を互いに比較し、新しい行を作成して既存のものを更新しています。完全な比較が約 1.5 時間続き、2 つのコアのうち 1 つだけがビジーであることに気付きました (タスク マネージャーは 50-52% の CPU 使用率です)。私は並列 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

: daContactand daContactDetailsare SqlDataAdapterssourceand destareDataSetsおよびContactand and ContactDetailareDataTablesであり、すべての ContactDetail は Contact に属します。

両方のコアが 100% の CPU を使用するわけではありませんが、2 番目のコアはほとんどアイドル状態であるため、クエリを並列化するとパフォーマンスが大幅に向上すると思います。for eachチケットは互いに関連していないため、最適化するのに適した場所でもあります。したがって、複数のスレッドでループし、レコードを並行して作成/更新できると思います。しかし、PLINQ でそれを行うにはどうすればよいでしょうか。

サイドノート:コメントで述べたように、サーバーの唯一の目的はMySQLデータベース(別のサーバー上)をMS SQL-Server(同じサーバー上)と同期させることであるため、これまでのところパフォーマンスは重要な要素ではありませんこの Windows サービスとして)。これは、別のサービスによって生成されるレポートのソースとして機能します。ただし、これらのレポートは 1 日に 1 回しか生成されません。しかし、それとは別に、私は PLINQ の学習に興味を持っていました。これは優れた演習になると考えたからです。宛先 DB が空で、すべてのレコードを作成する必要がある場合にのみ、前述の 1.5 時間かかります。両方のデータベースがほぼ同期している場合、この方法にかかる時間はまだ 1 分程度です。将来的には、メール以来、パフォーマンスがより重要になりますいくつかの連絡先タイプの 1 つにすぎません (チャット + 通話は 1mil.records を超えます)。とにかく、ある種の(LINQ)データページングが必要になると思います。

何か不明な点がある場合は、それに応じて回答を更新します。前もって感謝します。


編集:これが私の調査と試みの結果です:

質問: 結合を使用して既存の LINQ クエリを "PLINQ" する方法は?

回答: 一部の LINQ 演算子はバイナリであることに注意してください。入力として 2 つの 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 および Namespace の一部ですSytem.Threading.Tasks

クエリはシーケンシャル モードで十分に高速であるため、並列化から利益を得られないことに気付きましたExcept(両方のコレクションの行数がほぼ同じで、比較の最大数が得られる場合でも、30 秒未満で結果が得られます)。 )。しかし、後で完全を期すために追加します。

そこでfor-each、LINQ クエリと同じくらい簡単なものを並列化することにAsParallel()しました。最後に追加するだけです。しかし、並列処理を強制する必要があることに気付きましたWithExecutionMode(ParallelExecutionMode.ForceParallelism)。そうしないと、.NET はこのループに 1 つのコアのみを使用することを決定します。また、できるだけ多くのスレッドを使用したいが、8 個を超えないように .NET に伝えたかったのです。WithDegreeOfParallelism(8).

現在、両方のコアが同時に動作していますが、CPU 使用率は 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と( AsParallelhh: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、同様の時間の責任ですか?


推奨される読み物は次のとおりです。

4

1 に答える 1

1

さらに調査する価値のある 3 つのポイントがあります。

  1. .toList() を使用しないでください。私は間違っているかもしれませんが、さらに最適化が可能であれば、.ToList をこのように使用すると、コンパイラがクエリを最適化できなくなると思います。
  2. 独自のフィルタリング操作を使用して、両方の宛先からのデータを比較します。パフォーマンスが向上する可能性があります。
  3. LinqDataviewを使用してパフォーマンスを向上できるかどうかを確認してください。

    挿入中に PLinq の利点が得られるとは思いません。詳細については、この回答をご覧ください。

それが役立つことを願っています。上記の点について説明が必要な場合は、お尋ねください。

于 2011-10-06T22:54:31.970 に答える