Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Paralel LINQ (PLINQ) adalah implementasi paralel dari pola Language-Integrated Query (LINQ). PLINQ mengimplementasikan serangkaian operator kueri lengkap dari LINQ sebagai metode ekstensi untuk System.Linq namespace dan memiliki operator tambahan untuk operasi paralel. PLINQ menggabungkan kesederhanaan dan keterbacaan sintaks LINQ dengan kekuatan pemrograman paralel.
Petunjuk / Saran
Jika Anda tidak terbiasa dengan LINQ, ini menampilkan model terpadu untuk melakukan kueri pada sumber data yang dapat dienumerasi dengan cara yang aman terhadap tipe. LINQ ke Objek adalah nama untuk kueri LINQ yang dijalankan terhadap koleksi dalam memori seperti List<T> dan array. Artikel ini mengasumsikan bahwa Anda memiliki pemahaman dasar tentang LINQ. Untuk informasi selengkapnya, lihat KueriLanguage-Integrated (LINQ).
Apa itu kueri Paralel?
Kueri PLINQ dalam banyak hal menyerupai kueri LINQ ke Objek non-paralel. Kueri PLINQ, sama seperti kueri LINQ berurutan, beroperasi pada sumber data dalam memori IEnumerable atau IEnumerable<T> apa pun, dan memiliki eksekusi tertunda, yang berarti mereka tidak mulai mengeksekusi sampai kueri didaftar. Perbedaan utamanya adalah bahwa PLINQ mencoba untuk menggunakan penuh semua prosesor pada sistem. Ini dilakukan dengan mempartisi sumber data ke dalam segmen, lalu menjalankan kueri pada setiap segmen pada utas pekerja terpisah secara paralel pada beberapa prosesor. Dalam banyak kasus, eksekusi paralel berarti bahwa kueri berjalan secara signifikan lebih cepat.
Melalui eksekusi paralel, PLINQ dapat mencapai peningkatan performa yang signifikan atas kode warisan untuk jenis kueri tertentu, sering kali hanya dengan menambahkan AsParallel operasi kueri ke sumber data. Namun, paralelisme dapat memperkenalkan kompleksitasnya sendiri, dan tidak semua operasi kueri berjalan lebih cepat di PLINQ. Bahkan, paralelisasi sebenarnya memperlambat kueri tertentu. Oleh sebab itu, Anda harus memahami bagaimana isu seperti pengurutan memengaruhi kueri paralel. Untuk informasi selengkapnya, lihat Memahami Speedup di PLINQ.
Nota
Dokumentasi ini menggunakan ekspresi lambda untuk menentukan delegasi di PLINQ. Jika Anda tidak terbiasa dengan ekspresi lambda di C# atau Visual Basic, lihat Ekspresi Lambda di PLINQ dan TPL.
Sisa artikel ini memberikan gambaran umum tentang kelas PLINQ utama dan membahas cara membuat kueri PLINQ. Setiap bagian berisi tautan ke informasi dan contoh kode yang lebih rinci.
Kelas ParallelEnumerable
Kelas ini System.Linq.ParallelEnumerable mengekspos hampir semua fungsionalitas PLINQ. Ini dan jenis namespace System.Linq lainnya dikompilasi ke dalam assembly System.Core.dll. Proyek C# dan Visual Basic bawaan di Visual Studio mereferensikan rakitan dan mengimpor namespace.
ParallelEnumerable termasuk implementasi semua operator kueri standar yang didukung LINQ ke Objek, meskipun tidak mencoba untuk memparallelkan masing-masingnya. Jika Anda tidak terbiasa dengan LINQ, lihat Pengantar LINQ (C#) dan Pengenalan LINQ (Visual Basic).
Selain operator kueri standar, ParallelEnumerable kelas berisi sekumpulan metode yang memungkinkan perilaku khusus untuk eksekusi paralel. Metode khusus PLINQ ini tercantum dalam tabel berikut.
| ParallelEnumerable Operator | Deskripsi |
|---|---|
| AsParallel | Titik masuk untuk PLINQ. Menentukan bahwa kueri lainnya harus diparalelkan, jika memungkinkan. |
| AsSequential | Menentukan bahwa kueri lainnya harus dijalankan secara berurutan, sebagai kueri LINQ non-paralel. |
| AsOrdered | Menentukan bahwa PLINQ harus mempertahankan urutan sumber untuk kueri lainnya, atau sampai pengurutan diubah, misalnya dengan menggunakan klausa orderby (Order By in Visual Basic). |
| AsUnordered | Menentukan bahwa PLINQ untuk kueri lainnya tidak diperlukan untuk mempertahankan urutan sumber. |
| WithCancellation | Menentukan bahwa PLINQ harus secara berkala memantau status token pembatalan yang disediakan dan membatalkan eksekusi jika diminta. |
| WithDegreeOfParallelism | Menentukan jumlah maksimum prosesor yang harus digunakan PLINQ untuk menyejajarkan kueri. |
| WithMergeOptions | Memberikan petunjuk tentang cara PLINQ seharusnya, jika memungkinkan, menggabungkan hasil paralel menjadi satu rangkaian pada utas pemrosesan. |
| WithExecutionMode | Menentukan apakah PLINQ harus menyejajarkan kueri bahkan ketika perilaku default akan menjalankannya secara berurutan. |
| ForAll | Metode enumerasi multithread yang, tidak seperti iterasi atas hasil kueri, memungkinkan hasil diproses secara paralel tanpa harus mengembalikan hasil ke utas konsumen terlebih dahulu. |
| Aggregate Berlebihan | Kelebihan beban yang unik untuk PLINQ dan memungkinkan agregasi perantara melalui partisi thread-local, ditambah fungsi agregasi akhir untuk menggabungkan hasil semua partisi. |
Model Pilihan Keikutsertaan
Saat Anda menulis kueri, ikut serta ke PLINQ dengan memanggil ParallelEnumerable.AsParallel metode ekstensi pada sumber data, seperti yang ditunjukkan dalam contoh berikut.
var source = Enumerable.Range(1, 10000);
// Opt in to PLINQ with AsParallel.
var evenNums = from num in source.AsParallel()
where num % 2 == 0
select num;
Console.WriteLine($"{evenNums.Count()} even numbers out of {source.Count()} total");
// The example displays the following output:
// 5000 even numbers out of 10000 total
Dim source = Enumerable.Range(1, 10000)
' Opt in to PLINQ with AsParallel
Dim evenNums = From num In source.AsParallel()
Where num Mod 2 = 0
Select num
Console.WriteLine("{0} even numbers out of {1} total",
evenNums.Count(), source.Count())
' The example displays the following output:
' 5000 even numbers out of 10000 total
Metode ekstensi AsParallel mengikat operator kueri berikutnya, dalam hal ini, where dan select, dengan implementasi System.Linq.ParallelEnumerable.
Mode Pelaksanaan
Secara default, PLINQ konservatif. Pada runtime, infrastruktur PLINQ menganalisis struktur keseluruhan kueri. Jika kueri cenderung menghasilkan percepatan dengan paralelisasi, PLINQ mempartisi urutan sumber ke dalam tugas yang dapat dijalankan secara bersamaan. Jika tidak aman untuk menyejajarkan kueri, PLINQ hanya menjalankan kueri secara berurutan. Jika PLINQ memiliki pilihan antara algoritma paralel yang berpotensi mahal atau algoritma berurutan yang murah, PLINQ memilih algoritma berurutan secara default. Anda dapat menggunakan WithExecutionMode metode dan System.Linq.ParallelExecutionMode enumerasi untuk menginstruksikan PLINQ untuk memilih algoritma paralel. Ini berguna ketika Anda tahu dengan menguji dan mengukur bahwa kueri tertentu dijalankan lebih cepat secara paralel. Untuk informasi selengkapnya, lihat Cara: Menentukan Mode Eksekusi di PLINQ.
Tingkat Paralelisme
Secara default, PLINQ menggunakan semua prosesor di komputer host. Anda dapat menginstruksikan PLINQ untuk menggunakan tidak lebih dari jumlah prosesor tertentu dengan menggunakan metode .WithDegreeOfParallelism Ini berguna ketika Anda ingin memastikan bahwa proses lain yang berjalan di komputer menerima sejumlah waktu CPU. Cuplikan berikut membatasi kueri untuk menggunakan maksimal dua prosesor.
var query = from item in source.AsParallel().WithDegreeOfParallelism(2)
where Compute(item) > 42
select item;
Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)
Where Compute(item) > 42
Select item
Dalam kasus di mana kueri melakukan sejumlah besar pekerjaan yang tidak terkait komputasi seperti input/output berkas, mungkin bermanfaat untuk menentukan tingkat paralelisme yang lebih besar dari jumlah inti pada komputer.
Kueri Paralel yang Diurutkan Versus Tidak Diurutkan
Dalam beberapa kueri, operator kueri harus menghasilkan hasil yang mempertahankan urutan sumber. PLINQ menyediakan AsOrdered operator untuk tujuan ini. AsOrdered berbeda dari AsSequential. AsOrdered Urutan masih diproses secara paralel, tetapi hasilnya di-buffer dan diurutkan. Karena pemeliharaan urutan biasanya melibatkan pekerjaan ekstra, AsOrdered urutan mungkin diproses lebih lambat daripada urutan default AsUnordered. Apakah operasi paralel tertentu yang diurutkan lebih cepat daripada versi berurutan dari operasi tergantung pada banyak faktor.
Contoh kode berikut menunjukkan cara ikut serta dalam pemeliharaan urutan.
var evenNums =
from num in numbers.AsParallel().AsOrdered()
where num % 2 == 0
select num;
Dim evenNums = From num In numbers.AsParallel().AsOrdered()
Where num Mod 2 = 0
Select num
Untuk informasi selengkapnya, lihat Pelestarian Pesanan di PLINQ.
Kueri Paralel vs. Serial
Beberapa operasi mengharuskan data sumber dikirimkan secara berurutan. Operator ParallelEnumerable kueri kembali ke mode berurutan secara otomatis saat diperlukan. Untuk operator kueri dan delegasi yang ditentukan oleh pengguna yang memerlukan eksekusi berurutan, PLINQ menyediakan metode AsSequential. Saat Anda menggunakan AsSequential, semua operator berikutnya dalam kueri dijalankan secara berurutan hingga AsParallel dipanggil lagi. Untuk informasi selengkapnya, lihat Cara: Menggabungkan Kueri LINQ Paralel dan Berurutan.
Opsi untuk Menggabungkan Hasil Kueri
Saat kueri PLINQ dijalankan secara paralel, hasil dari setiap worker thread harus digabungkan kembali ke utas utama untuk dikonsumsi oleh foreach perulangan (For Each di Visual Basic), atau penyisipan ke dalam daftar atau array. Dalam beberapa kasus, mungkin bermanfaat untuk menentukan jenis operasi penggabungan tertentu, misalnya, untuk mulai menghasilkan hasil dengan lebih cepat. Untuk tujuan ini, PLINQ mendukung metode WithMergeOptions dan enumerasi ParallelMergeOptions. Untuk informasi selengkapnya, lihat Opsi Penggabungan di PLINQ.
The ForAll Operator
Dalam kueri LINQ berurutan, eksekusi ditangguhkan hingga kueri dijumlahkan baik dalam perulangan foreach (For Each di Visual Basic) atau dengan memanggil metode seperti ToList , , ToArray atau ToDictionary. Di PLINQ, Anda juga dapat menggunakan foreach untuk menjalankan kueri dan melakukan iterasi melalui hasilnya. Namun, foreach sendiri tidak berjalan secara paralel, dan oleh karena itu, mengharuskan keluaran dari semua tugas paralel digabungkan kembali ke dalam utas tempat perulangan berjalan. Di PLINQ, Anda dapat menggunakan foreach saat Anda harus mempertahankan urutan akhir hasil kueri, dan juga setiap kali Anda memproses hasilnya secara serial, misalnya saat Anda memanggil Console.WriteLine untuk setiap elemen. Untuk eksekusi kueri yang lebih cepat ketika preservasi pesanan tidak diperlukan dan ketika pemrosesan hasil itu sendiri dapat diparalelkan, gunakan ForAll metode untuk menjalankan kueri PLINQ.
ForAll tidak melakukan langkah penggabungan akhir ini. Contoh kode berikut menunjukkan cara menggunakan ForAll metode .
System.Collections.Concurrent.ConcurrentBag<T> digunakan di sini karena dioptimalkan untuk beberapa utas yang beroperasi secara bersamaan dalam menambahkan tanpa mencoba menghapus item apa pun.
var nums = Enumerable.Range(10, 10000);
var query =
from num in nums.AsParallel()
where num % 10 == 0
select num;
// Process the results as each thread completes
// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll(e => concurrentBag.Add(Compute(e)));
Dim nums = Enumerable.Range(10, 10000)
Dim query = From num In nums.AsParallel()
Where num Mod 10 = 0
Select num
' Process the results as each thread completes
' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))
Ilustrasi berikut menunjukkan perbedaan antara foreach dan ForAll sehubungan dengan eksekusi kueri.
Pembatalan
PLINQ terintegrasi dengan jenis pembatalan di .NET. (Untuk informasi selengkapnya, lihat Pembatalan di Utas Terkelola.) Oleh karena itu, tidak seperti LINQ berurutan dengan kueri Objek, kueri PLINQ dapat dibatalkan. Untuk membuat kueri PLINQ yang dapat dibatalkan, gunakan WithCancellation operator pada kueri dan berikan CancellationToken instans sebagai argumen. Ketika properti IsCancellationRequested pada token diatur ke true, PLINQ akan mendeteksi hal tersebut, menghentikan pemrosesan pada semua utas, dan menghasilkan OperationCanceledException.
Ada kemungkinan bahwa kueri PLINQ mungkin terus memproses beberapa elemen setelah token pembatalan diatur.
Untuk responsivitas yang lebih baik, Anda juga dapat menanggapi permintaan pembatalan di delegasi pengguna dengan durasi panjang. Untuk informasi selengkapnya, lihat Cara: Membatalkan Kueri PLINQ.
Pengecualian
Saat kueri PLINQ dijalankan, beberapa pengecualian mungkin dilemparkan dari utas yang berbeda secara bersamaan. Selain itu, kode untuk menangani pengecualian mungkin berada di utas yang berbeda dari kode yang melemparkan pengecualian. PLINQ menggunakan tipe AggregateException untuk merangkum semua pengecualian yang dilemparkan oleh kueri, dan mengatur kembali pengecualian tersebut ke utas yang memanggil. Pada utas panggilan, hanya satu blok try-catch yang diperlukan. Namun, Anda dapat melakukan iterasi melalui semua pengecualian yang dienkapsulasi dalam AggregateException dan menangkap setiap pengecualian yang dapat Anda tangani dengan aman. Dalam kasus yang jarang terjadi, beberapa pengecualian mungkin dilemparkan yang tidak dibungkus dalam AggregateException, dan ThreadAbortException juga tidak dibungkus.
Ketika pengecualian diizinkan untuk menggelegak kembali ke utas penggabungan, maka ada kemungkinan bahwa kueri dapat terus memproses beberapa item setelah pengecualian dinaikkan.
Untuk informasi selengkapnya, lihat Cara: Menangani Pengecualian dalam Kueri PLINQ.
Partisi Kustom
Dalam beberapa kasus, Anda dapat meningkatkan performa kueri dengan menulis partisi kustom yang memanfaatkan beberapa karakteristik data sumber. Dalam kueri, partisi kustom itu sendiri adalah objek enumerable yang menjadi sasaran kueri.
int[] arr = new int[9999];
Partitioner<int> partitioner = new MyArrayPartitioner<int>(arr);
var query = partitioner.AsParallel().Select(SomeFunction);
Dim arr(10000) As Integer
Dim partitioner As Partitioner(Of Integer) = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))
PLINQ mendukung jumlah partisi tetap (meskipun data dapat ditetapkan ulang secara dinamis ke partisi tersebut selama runtime untuk penyeimbangan beban.). For dan ForEach hanya mendukung partisi dinamis, yang berarti bahwa jumlah partisi berubah pada runtime. Untuk informasi selengkapnya, lihat Pemartisi Kustom untuk PLINQ dan TPL.
Mengukur Performa PLINQ
Dalam banyak kasus, kueri dapat dijalankan secara paralel, tetapi beban tambahan dari penyiapan kueri paralel melebihi peningkatan kinerja yang diperoleh. Jika kueri tidak melakukan banyak komputasi atau jika sumber data kecil, kueri PLINQ mungkin lebih lambat daripada kueri LINQ berurutan ke Objek. Anda dapat menggunakan Parallel Performance Analyzer di Visual Studio Team Server untuk membandingkan performa berbagai kueri, untuk menemukan hambatan pemrosesan, dan untuk menentukan apakah kueri Anda berjalan secara paralel atau berurutan. Untuk informasi selengkapnya, lihat Visualizer Konkurensi dan Cara: Mengukur Performa Kueri PLINQ.