Pengantar LINQ

Parallel LINQ (PLINQ) adalah implementasi paralel dari pola Language-Integrated Query (LINQ ). PLINQ mengimplementasikan set lengkap operator kueri standar LINQ sebagai metode ekstensi untuk System.Linq namespace layanan dan memiliki operator tambahan untuk operasi paralel. PLINQ menggabungkan kesederhanaan dan keterbacaan sintaks LINQ dengan kekuatan pemrograman paralel.

Tip

Jika Anda tidak terbiasa dengan LINQ, ini menampilkan model terpadu untuk mengkueri sumber data yang dapat dijumlahkan dengan cara yang aman jenis. LINQ ke Objek adalah nama untuk kueri LINQ yang dijalankan terhadap kumpulan dalam memori seperti List<T> dan array. Panduan ini mengasumsikan bahwa Anda telah memiliki pemahaman dasar tentang LINQ. Untuk informasi selengkapnya, lihat Kueri Terintegrasi Bahasa (LINQ).

Apa itu kueri Paralel?

Kueri PLINQ dalam banyak hal menyerupan LINQ non-paralel ke kueri Objek. Kueri PLINQ, sama seperti kueri LINQ berurutan, beroperasi pada setiap dalam memori IEnumerable atau IEnumerable<T> sumber data, dan telah menunda eksekusi, yang berarti mereka tidak mulai mengeksekusi sampai kueri dijumlahkan. Perbedaan utamanya adalah bahwa PLINQ mencoba untuk menggunakan sepenuhnya 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 penentuan huruf, eksekusi paralel berarti 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 karena itu, Anda harus memahami bagaimana masalah seperti pengurutan memengaruhi kueri paralel. Untuk informasi selengkapnya, lihat Memahami Percepatan di PLINQ.

Catatan

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 System.Linq namespace layanan lainnya dikompilasi ke dalam assembly System.Core.dll. Proyek C# dan Visual Basic default di Visual Studio mereferensikan assembly dan mengimpor namespace layanan.

ParallelEnumerable mencakup implementasi semua operator kueri standar yang didukung LINQ ke Objek, meski tidak mencoba untuk menyejajarkan masing-masing operator kueri. 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 (Urutkan Menurut dalam 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 bagaimana PLINQ seharusnya, jika memungkinkan, gabungkan hasil paralel kembali ke hanya satu urutan pada utas yang mengonsumsi.
WithExecutionMode Menentukan apakah PLINQ harus memparalelkan kueri bahkan saat perilaku default akan menjalankannya secara berurutan.
ForAll Metode enumerasi multithread yang, tidak seperti iterasi atas hasil kueri, memungkinkan hasil diproses secara paralel tanpa terlebih dahulu menggabungkan kembali ke utas konsumen.
Aggregate overload Overload yang unik untuk PLINQ dan memungkinkan agregasi perantara melalui partisi utas-lokal, ditambah fungsi agregasi akhir untuk menggabungkan hasil semua partisi.

Model Keikutsertaan

Saat Anda menulis kueri, pilih 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("{0} even numbers out of {1} total",
                  evenNums.Count(), source.Count());
// 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 AsParallel ekstensi mengikat operator kueri berikutnya, dalam hal ini, where dan select, ke System.Linq.ParallelEnumerable implementasi.

Mode Eksekusi

Secara default, PLINQ bersifat konservatif. Pada durasi, 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 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 saat 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 saat 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 penentuan huruf di mana kueri melakukan sejumlah besar pekerjaan yang tidak terikat komputasi seperti File I/O, mungkin bermanfaat untuk menentukan tingkat paralelisme yang lebih besar dari jumlah inti pada komputer.

Kueri Paralel yang Diurutkan Versus Tidak Berurutan

Dalam beberapa kueri, operator kueri harus menghasilkan hasil yang mempertahankan urutan sumber. PLINQ menyediakan AsOrdered operator untuk tujuan ini. AsOrdered berbeda dari AsSequential. Urutan AsOrdered masih diproses secara paralel, tetapi hasilnya di-buffer dan diurutkan. Karena preservasi pesanan 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 operasi tergantung pada banyak faktor.

Contoh kode berikut menunjukkan cara ikut serta dalam preservasi pesanan.

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 Preservasi Pesanan di PLINQ.

Kueri Paralel vs. Berurutan

Beberapa operasi mengharuskan data sumber dikirimkan secara berurutan. Operator ParallelEnumerable kueri kembali ke mode berurutan secara otomatis saat diperlukan. Untuk operator kueri yang ditentukan pengguna dan delegasi pengguna yang memerlukan eksekusi berurutan, PLINQ menyediakan AsSequential metode . 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, hasilnya dari setiap utas pekerja harus digabungkan kembali ke utas utama untuk dikonsumsi oleh perulangan foreach (For Each dalam Visual Basic), atau penyisipan ke dalam daftar atau array. Dalam beberapa penentuan huruf, 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.

Operator ForAll

Dalam kueri LINQ berurutan, eksekusi ditangguhkan hingga kueri dijumlahkan baik dalam perulangan foreach (For Each dalam 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 itu sendiri tidak berjalan secara paralel, dan oleh karena itu, mengharuskan output dari semua tugas paralel digabungkan kembali ke 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 setiap elemen. Untuk eksekusi kueri yang lebih cepat saat preservasi pesanan tidak diperlukan dan saat pemrosesan hasil itu sendiri dapat diparalelkan, gunakan ForAll metode untuk menjalankan kueri PLINQ. ForAll tidak melakukan langkah penggabungan akhir ini. Kode contoh berikut menunjukkan cara menggunakan metode ForAll. System.Collections.Concurrent.ConcurrentBag<T> digunakan di sini karena dioptimalkan untuk beberapa utas yang ditambahkan secara bersamaan 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 ini memperlihatkan perbedaan antara foreach dan ForAll sehubungan dengan eksekusi kueri.

ForAll vs. ForEach

Pembatalan

PLINQ terintegrasi dengan jenis pembatalan di .NET. (Untuk informasi selengkapnya, lihat Pembatalan di Utas Terkelola.) Oleh karena itu, tidak seperti kueri LINQ berurutan ke Objek, kueri PLINQ dapat dibatalkan. Untuk membuat kueri PLINQ yang dapat dibatalkan, gunakan WithCancellation operator pada kueri dan berikan instans CancellationToken sebagai argumen. Saat properti IsCancellationRequested pada token diatur ke true, PLINQ akan melihatnya, berhenti memproses pada semua utas, dan melempar OperationCanceledException.

Ada kemungkinan bahwa kueri PLINQ mungkin terus memproses beberapa elemen setelah token pembatalan diatur.

Untuk responsivitas yang lebih besar, Anda juga dapat menanggapi permintaan pembatalan di delegasi pengguna yang berjalan lama. 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 AggregateException jenis untuk merangkum semua pengecualian yang dilemparkan oleh kueri, dan marshal pengecualian tersebut kembali ke utas panggilan. Pada utas panggilan, hanya diperlukan satu blok try-catch. Namun, Anda dapat melakukan iterasi melalui semua pengecualian yang dienkapsulasi dalam AggregateException dan mengambil apa pun yang dapat Anda pulihkan dengan aman. Dalam penentuan huruf yang jarang terjadi, beberapa pengecualian mungkin dilemparkan yang tidak dibungkus dalam AggregateException, dan ThreadAbortExceptionjuga tidak dibungkus.

Ketika pengecualian diizinkan untuk naik kembali ke alur untuk bergabung, maka ada kemungkinan bahwa kueri dapat terus memproses beberapa item setelah pengecualian dimunculkan.

Untuk informasi selengkapnya, lihat Cara: Menangani Pengecualian dalam Kueri PLINQ.

Partisi Kustom

Dalam beberapa penentuan huruf, 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 dikueri.

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 (meski data dapat ditetapkan kembali secara dinamis ke partisi tersebut selama durasi untuk penyeimbangan beban.). For dan ForEach hanya mendukung partisi dinamis, yang berarti bahwa jumlah partisi berubah pada durasi. Untuk informasi selengkapnya, lihat Partisi Kustom untuk PLINQ dan TPL.

Mengukur Performa PLINQ

Dalam banyak penentuan huruf, kueri dapat diparalelkan, tetapi overhead pengaturan kueri paralel melebihi manfaat performa 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 Penganalisis Kinerja Paralel di Visual Studio Team Server untuk membandingkan performa berbagai kueri, untuk menemukan penyempitan pemrosesan, dan untuk menentukan apakah kueri Anda berjalan secara paralel atau berurutan. Untuk informasi selengkapnya, lihat Visualizer Konkurensi dan Cara: Mengukur Performa Kueri PLINQ.

Lihat juga