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
注: daContact
and daContactDetails
are SqlDataAdapters
、source
and dest
areDataSets
およびContact
and and ContactDetail
areDataTables
であり、すべての 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
と( 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
、同様の時間の責任ですか?
推奨される読み物は次のとおりです。