Kelas ParallelHelper

Kelas ParallelHelper berisi API performa tinggi untuk bekerja dengan kode paralel. Ini berisi metode berorientasi performa yang dapat digunakan untuk menyiapkan dan menjalankan operasi paralel dengan cepat melalui himpunan data atau rentang atau area perulangan tertentu.

API Platform:ParallelHelper, IAction, IAction2D, IRefAction<T>, IInAction<T>

Cara kerjanya

Jenis ParallelHelper ini dibangun di sekitar tiga konsep utama:

  • Ini melakukan batching otomatis di atas rentang iterasi target. Fitur ini secara otomatis menjadwalkan jumlah unit kerja yang tepat berdasarkan jumlah inti CPU yang tersedia. Fitur ini mengurangi overhead pemanggilan panggilan balik paralel sekali untuk setiap iterasi paralel.
  • Ini sangat memanfaatkan implementasi jenis generik dalam C#. Ini menggunakan struct tipe yang mengimplementasikan antarmuka spesifik alih-alih delegasi seperti Action<T>. Dengan menggunakan pendekatan ini, pengkompilasi JIT dapat "melihat" setiap jenis panggilan balik individu yang digunakan, yang berarti dapat meng-sebariskan panggilan balik sepenuhnya jika memungkinkan. Pendekatan ini sangat mengurangi overhead dari setiap iterasi paralel, terutama ketika Anda menggunakan panggilan balik yang sangat kecil. Overhead pemanggilan delegasi pada dasarnya tidak akan signifikan. Selain itu, dengan menggunakan struct tipe sebagai fungsi panggilan balik, Anda perlu secara manual menangani variabel yang ditangkap dalam closure. Persyaratan ini mencegah penangkapan pointer this yang tidak disengaja dari metode instans dan nilai lain yang dapat secara signifikan memperlambat tiap pemanggilan balik. Pendekatan ini sama dengan yang digunakan di pustaka berorientasi performa lainnya seperti ImageSharp.
  • Ini mengekspos empat jenis API yang mewakili empat jenis iterasi yang berbeda: perulangan 1D dan 2D, perulangan item dengan efek samping, dan perulangan item tanpa efek samping. Setiap jenis tindakan memiliki tipe yang sesuai interface yang perlu diterapkan ke struct panggilan balik yang Anda teruskan ke ParallelHelper API. Antarmuka ini adalah IAction, , IAction2DIRefAction<T>, dan IInAction<T>. Dengan menggunakan antarmuka ini, Anda dapat menulis kode yang dengan jelas mengekspresikan niatnya, dan API dapat melakukan pengoptimalan lebih lanjut secara internal.

Sintaks

Katakanlah kita tertarik untuk memproses semua item dalam float[] array dan mengalikan masing-masing dengan 2. Dalam hal ini, kita tidak perlu mengambil variabel apa pun: kita hanya dapat menggunakan IRefAction<T>interface dan ParallelHelper akan memuat setiap item untuk disalurkan ke panggilan balik secara otomatis. Yang diperlukan adalah mendefinisikan panggilan balik kami, yang akan menerima ref float argumen dan melakukan operasi yang diperlukan:

// Be sure to include this using at the top of the file:
using Microsoft.Toolkit.HighPerformance.Helpers;

// First declare the struct callback
public readonly struct ByTwoMultiplier : IRefAction<float>
{
    public void Invoke(ref float x) => x *= 2;
}

// Create an array and run the callback
float[] array = new float[10000];

ParallelHelper.ForEach<float, ByTwoMultiplier>(array);

ForEach Dengan API, kita tidak perlu menentukan rentang iterasi: ParallelHelper akan mengumpulkan koleksi dan memproses setiap item input secara otomatis. Selain itu, dalam contoh spesifik ini kami bahkan tidak perlu meneruskan kami struct sebagai argumen: karena tidak berisi bidang apa pun yang perlu kami inisialisasi, kami hanya dapat menentukan jenisnya sebagai argumen jenis saat memanggil ParallelHelper.ForEach: API tersebut kemudian akan membuat instans baru itu struct sendiri, dan menggunakannya untuk memproses berbagai item.

Untuk memperkenalkan konsep penutupan, misalkan kita ingin mengalikan elemen array dengan nilai yang ditentukan saat runtime. Untuk melakukannya, kita perlu "menangkap" nilai tersebut dalam jenis panggilan balik struct kita. Kita bisa melakukannya seperti itu:

public readonly struct ItemsMultiplier : IRefAction<float>
{
    private readonly float factor;

    public ItemsMultiplier(float factor)
    {
        this.factor = factor;
    }

    public void Invoke(ref float x) => x *= this.factor;
}

// ...

ParallelHelper.ForEach(array, new ItemsMultiplier(3.14f));

Kita dapat melihat bahwa struct sekarang berisi bidang yang mewakili faktor yang ingin kita gunakan untuk mengalikan elemen, alih-alih menggunakan konstanta. Dan saat memanggil ForEach, kami secara eksplisit membuat instance dari tipe callback kami, dengan faktor yang menarik bagi kami. Selain itu, dalam hal ini pengkompilasi C# juga dapat secara otomatis mengenali argumen jenis yang kita gunakan, sehingga kita dapat menghilangkannya bersama-sama dari pemanggilan metode.

Pendekatan membuat bidang untuk nilai yang perlu kita akses dari panggilan balik ini memungkinkan kita secara eksplisit menyatakan nilai apa yang ingin kita tangkap, yang membantu membuat kode lebih ekspresif. Ini adalah hal yang sama persis dengan yang dilakukan pengkompilasi C# di belakang layar ketika kita mendeklarasikan fungsi lambda atau fungsi lokal yang mengakses beberapa variabel lokal juga.

Berikut adalah contoh lain, kali ini menggunakan For API untuk menginisialisasi semua item array secara paralel. Perhatikan bagaimana kali ini kami menangkap array target secara langsung, dan kami menggunakan IActioninterface untuk panggilan balik kami, yang memberikan metode kami indeks perulangan paralel saat ini sebagai argumen:

public readonly struct ArrayInitializer : IAction
{
    private readonly int[] array;

    public ArrayInitializer(int[] array)
    {
        this.array = array;
    }

    public void Invoke(int i)
    {
        this.array[i] = i;
    }
}

// ...

ParallelHelper.For(0, array.Length, new ArrayInitializer(array));

Catatan

Karena jenis callback adalah struct-s, mereka diteruskan dengan salinan ke setiap utas yang berjalan secara paralel, bukan sebagai referensi. Ini berarti bahwa jenis nilai yang disimpan sebagai bidang dalam jenis panggilan balik juga akan disalin. Praktik yang baik untuk mengingat detail itu dan menghindari kesalahan adalah menandai panggilan balik struct sebagai readonly, sehingga pengkompilasi C# tidak akan membiarkan kita memodifikasi nilai bidangnya. Ini hanya berlaku untuk bidang instans dari jenis nilai: jika panggilan balik struct memiliki static bidang jenis apa pun, atau bidang referensi, nilai tersebut akan dibagikan dengan benar di antara utas paralel.

Metode

Ini adalah 4 API utama yang diekspos oleh ParallelHelper, sesuai dengan antarmuka IAction, IAction2D, IRefAction<T>, dan IInAction<T>. Jenis ini ParallelHelper juga mengekspos sejumlah kelebihan beban untuk metode ini, yang menawarkan sejumlah cara untuk menentukan rentang perulangan, atau jenis panggilan balik input. For dan For2D bekerja pada IAction dan instans IAction2D, dan mereka dimaksudkan untuk digunakan ketika beberapa pekerjaan paralel perlu dilakukan yang tidak perlu memetakan ke koleksi yang mendasar yang dapat diakses langsung menggunakan indeks dari setiap iterasi paralel. Kelebihan ForEach beban sebagai gantinya wotk pada IRefAction<T> instans dan IInAction<T> , dan mereka dapat digunakan ketika iterasi paralel langsung memetakan ke item dalam koleksi yang dapat diindeks secara langsung. Dalam hal ini mereka juga mengabstraksi logika pengindeksan, sehingga setiap pemanggilan paralel hanya perlu fokus pada item input untuk diproses, dan bukan tentang cara mengambil item tersebut.

Contoh

Anda dapat menemukan lebih banyak contoh dalam pengujian unit.