Mengapa kelas mesin negara asinkron (dan bukan struct) di Roslyn?

87

Mari pertimbangkan metode asinkron yang sangat sederhana ini:

static async Task myMethodAsync() 
{
    await Task.Delay(500);
}

Ketika saya mengkompilasi ini dengan VS2013 (sebelum kompiler Roslyn) mesin negara yang dihasilkan adalah sebuah struct.

private struct <myMethodAsync>d__0 : IAsyncStateMachine
{  
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

Ketika saya mengkompilasinya dengan VS2015 (Roslyn) kode yang dihasilkan adalah ini:

private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
    ...
    void IAsyncStateMachine.MoveNext()
    {
        ...
    }
}

Seperti yang Anda lihat, Roslyn menghasilkan kelas (dan bukan struct). Jika saya ingat dengan benar implementasi pertama async / await dukungan di kompiler lama (CTP2012 kurasa) juga menghasilkan kelas dan kemudian diubah menjadi struct dari alasan kinerja. (dalam beberapa kasus, Anda dapat sepenuhnya menghindari tinju dan alokasi heap…) (Lihat ini )

Adakah yang tahu mengapa ini diubah lagi di Roslyn? (Saya tidak punya masalah tentang ini, saya tahu bahwa perubahan ini transparan dan tidak mengubah perilaku kode apa pun, saya hanya ingin tahu)

Edit:

Jawaban dari @Damien_The_Unbeliever (dan source code :)) imho menjelaskan semuanya. Perilaku yang dijelaskan dari Roslyn hanya berlaku untuk debug build (dan itu diperlukan karena batasan CLR yang disebutkan dalam komentar). Dalam Rilis itu juga menghasilkan struct (dengan semua manfaat itu ..). Jadi ini tampaknya menjadi solusi yang sangat cerdas untuk mendukung Edit dan Continue serta kinerja yang lebih baik dalam produksi. Hal-hal menarik, terima kasih untuk semua yang telah berpartisipasi!

gregkalapos.dll
sumber
2
Saya menduga bahwa mereka memutuskan kompleksitas (struct yang dapat diubah) tidak sepadan. asyncmetode hampir selalu memiliki titik asynchronous yang sebenarnya - awaityang menghasilkan kontrol, yang akan membutuhkan struct untuk dimasukkan ke dalam kotak. Saya percaya struct hanya akan mengurangi tekanan memori untuk asyncmetode yang berjalan secara sinkron.
Stephen Cleary

Jawaban:

112

Saya tidak memiliki pengetahuan sebelumnya tentang hal ini, tetapi karena Roslyn adalah open-source akhir-akhir ini, kita dapat menelusuri kode untuk mendapatkan penjelasan.

Dan di sini, di baris 60 dari AsyncRewriter , kami menemukan:

// The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class.
var typeKind = compilationState.Compilation.Options.EnableEditAndContinue ? TypeKind.Class : TypeKind.Struct;

Jadi, meskipun ada beberapa daya tarik untuk menggunakan structs, kemenangan besar memungkinkan Edit dan Lanjutkan untuk bekerja dalam asyncmetode jelas dipilih sebagai opsi yang lebih baik.

Damien_The_Tidak percaya
sumber
18
Tangkapan yang sangat bagus! Dan berdasarkan ini, inilah yang juga saya temukan: Ini hanya terjadi ketika Anda membangunnya di debug (masuk akal, saat itulah Anda melakukan EnC ..), tetapi dalam Rilis mereka membuat struct (jelas EnableEditAndContinue salah dalam kasus itu .. .). Btw. Saya juga mencoba mencari kode, tetapi tidak menemukan ini. Terimakasih banyak!
gregkalapos
3

Sulit untuk memberikan jawaban pasti untuk sesuatu seperti ini (kecuali seseorang dari tim kompilator mampir :)), tetapi ada beberapa hal yang dapat Anda pertimbangkan:

Kinerja "bonus" dari struct selalu merupakan pertukaran. Pada dasarnya, Anda mendapatkan yang berikut:

  • Nilai semantik
  • Alokasi tumpukan yang mungkin (bahkan mungkin mendaftar?)
  • Menghindari tipuan

Apa artinya ini dalam kasus menunggu? Sebenarnya ... tidak ada. Hanya ada periode waktu yang sangat singkat di mana mesin status ada di tumpukan - ingat, awaitsecara efektif melakukan a return, sehingga metode tumpukan mati; mesin negara harus dipertahankan di suatu tempat, dan "suatu tempat" pasti ada di heap. Masa pakai tumpukan tidak cocok dengan kode asinkron :)

Selain itu, mesin negara melanggar beberapa pedoman bagus untuk mendefinisikan struct:

  • structs harus berukuran paling banyak 16-byte - mesin negara berisi dua pointer, yang dengan sendirinya memenuhi batas 16-byte dengan rapi pada 64-bit. Selain itu, ada negara itu sendiri, sehingga melampaui "batas". Ini bukan masalah besar , karena kemungkinan besar hanya pernah diteruskan oleh referensi, tetapi perhatikan bagaimana hal itu tidak sesuai dengan kasus penggunaan untuk struct - struct yang pada dasarnya adalah tipe referensi.
  • structs harus tidak dapat diubah - ini mungkin tidak membutuhkan banyak komentar. Itu mesin negara . Sekali lagi, ini bukan masalah besar, karena struct adalah kode yang dibuat secara otomatis dan pribadi, tetapi ...
  • structs harus secara logis mewakili satu nilai. Jelas tidak terjadi di sini, tapi itu sudah mengikuti dari memiliki keadaan yang bisa berubah di tempat pertama.
  • Seharusnya tidak sering dimasukkan ke dalam kotak - tidak menjadi masalah di sini, karena kami menggunakan obat generik di mana-mana . Status pada akhirnya berada di suatu tempat di heap, tetapi setidaknya tidak dimasukkan dalam kotak (secara otomatis). Sekali lagi, fakta bahwa itu hanya digunakan secara internal membuat ini sangat tidak berlaku.

Dan tentu saja, semua ini terjadi jika tidak ada penutupan. Jika Anda memiliki penduduk lokal (atau bidang) yang melintasi awaits, statusnya meningkat lebih jauh, membatasi kegunaan penggunaan struct.

Mengingat semua ini, pendekatan kelas pasti lebih bersih, dan saya tidak akan mengharapkan peningkatan kinerja yang nyata dari penggunaan a struct. Semua objek yang terlibat memiliki seumur hidup yang sama, sehingga satu-satunya cara untuk meningkatkan kinerja memori akan membuat semua dari mereka structs (toko di beberapa penyangga, misalnya) - yang tidak mungkin dalam kasus umum, tentu saja. Dan sebagian besar kasus di mana Anda akan menggunakan awaitdi tempat pertama (yaitu, beberapa pekerjaan I / O asinkron) sudah melibatkan kelas lain - misalnya, buffer data, string ... Agak tidak mungkin Anda menginginkan awaitsesuatu yang hanya kembali 42tanpa melakukan apa pun alokasi heap.

Pada akhirnya, saya akan mengatakan satu-satunya tempat di mana Anda benar-benar melihat perbedaan kinerja yang nyata adalah tolok ukur. Dan mengoptimalkan tolok ukur adalah ide yang konyol, untuk sedikitnya ...

Luaan
sumber
Anda tidak selalu membutuhkan anggota tim kompilator ketika Anda dapat pergi dan membaca sumbernya, dan mereka meninggalkan komentar yang berguna :-)
Damien_The_Unbeliever
3
@Damien_The_Unbeliever Ya, itu benar-benar penemuan yang hebat, saya sudah memberi suara positif untuk jawaban Anda: P
Luaan
1
Struct banyak membantu dalam hal kode tidak berjalan async, misalnya data sudah ada dalam buffer.
Ian Ringrose