Beberapa hari yang lalu, N artikel diterbitkan yang mengatakan bahwa c#/.net terlalu lambat dan menuntut beberapa fitur c#/.net dihapus.
Terlepas dari artikel tersebut, tampaknya sudah menjadi aturan ketat yang diakui oleh industri bahwa c#/.net lambat Tidak peduli bagaimana semua orang membuktikan bahwa c#/.net tidak jauh lebih lambat dari c++, kinerja tingkat aplikasi masih sangat lambat. .
Jadi di manakah c#/.net lambat?
Sayangnya, sebagian besar program C# diperlambat oleh sebagian besar pemrogram. Kesimpulan ini mungkin tidak mudah diterima, tetapi kesimpulan ini tersebar luas.
Operasi string
Hampir semua program memiliki operasi String, dan setidaknya 90% di antaranya harus mengabaikan perbandingan huruf besar/kecil. Periksa kodenya.
jika (str1.ToUpper() == str2.ToUpper())
Atau versi ToLower, saya bahkan melihat ada Web HttpModule yang mengatakan:
untuk (int i = 0; i < strs.Hitungan; i++)
if (nilai.ToUpper() == strs[i].ToUpper())
//...
Coba pikirkan, setiap kali sebuah halaman diminta, potongan kode seperti itu harus dieksekusi untuk membuat instance string di bidang yang besar. Yang lebih dilebih-lebihkan adalah beberapa orang mengatakan bahwa ini adalah pertukaran ruang dengan waktu. . .
Pengujian kinerja
Jika cara ini dikatakan lambat, mungkin ada sebagian orang yang tidak mengakuinya dan menganggap ini adalah cara terbaik, sehingga disini kita perlu menggunakan tes khusus untuk membuktikan faktanya.
Pertama siapkan metode untuk menguji kinerja:
Kinerja Ukur TResult statis pribadi
{
GC.Kumpulkan();
int gc0 = GC.CollectionCount(0);
int gc1 = GC.CollectionCount(1);
int gc2 = GC.CollectionCount(2);
Hasil THasil = default(THasil);
Stopwatch sw = Stopwatch.MulaiBaru();
untuk (int i = 0; i < putaran; i++)
{
hasil = fungsi(arg);
}
Console.WriteLine(sw.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine("GC 0:" + (GC.CollectionCount(0) - gc0).ToString());
Console.WriteLine("GC 1:" + (GC.CollectionCount(1) - gc1).ToString());
Console.WriteLine("GC 2:" + (GC.CollectionCount(2) - gc2).ToString());
hasil pengembalian;
}
Kemudian siapkan string heap:
Daftar statis pribadi
{
Daftar
karakter[] chs = karakter baru[3];
untuk (int saya = 0; saya < 10000; saya++)
{
int j = saya;
for (int k = 0; k < chs.Panjang; k++)
{
chs[k] = (karakter)('a' + j % 26);
j = j / 26;
}
strs.Tambahkan(string baru(chs));
}
kembalikan str;
}
Mari kita lihat implementasi ToUpper:
bool statis pribadi ImplementByToUpper(Daftar
{
untuk (int i = 0; i < strs.Hitungan; i++)
if (nilai.ToUpper() == strs[i].ToUpper())
kembali benar;
kembali salah;
}
Terakhir siapkan metode utama:
Daftar
hasil yang buruk;
Console.WriteLine("Gunakan ImplementByToUpper");
hasil = MeasurePerformance(s => ImplementByToUpper(strs, s), "yZh", 1000);
Console.WriteLine("hasilnya adalah " + hasil.ToString());
Konsol.ReadLine();
Mari kita lihat hasil eksekusinya:
Gunakan ImplementByToUpper
2192 ms
Kejadian 0:247
Kejadian 1:0
Kejadian 2:0
hasilnya Benar
Mari kita lakukan uji komparatif dan gunakan string.Equals untuk menguji:
bool statis pribadi ImplementByStringEquals(Daftar
{
untuk (int i = 0; i < strs.Hitungan; i++)
if (string.Equals(nilai, strs[i], StringComparison.CurrentCultureIgnoreCase))
kembali benar;
kembali salah;
}
Mari kita lihat hasil eksekusinya:
Gunakan ImplementByStringEquals
1117ms
Kejadian 0:0
Kejadian 1:0
Kejadian 2:0
hasilnya Benar
Sebagai perbandingan, penggunaan ToUpper dua kali lebih lambat dan memiliki banyak objek sampah generasi 0. Mereka yang mengaku menukar ruang dengan waktu dapat merenungkan hal ini. Waktu negatif?
Penggunaan kelas kamus
Melanjutkan skenario string, beberapa orang mungkin berpikir untuk menggunakan tabel Hash dan struktur serupa lainnya untuk mempercepat. Ya, ini adalah ide yang bagus, tetapi tabel Hash tidak selalu merupakan solusi terbaik. Mari kita lakukan tes:
bool statis pribadi ImplementByHashSet(Daftar
{
HashSet
return set.Berisi(nilai);
}
Lihatlah hasil eksekusinya:
Gunakan ImplementByHashSet
5114ms
Kejadian 0:38
Kejadian 1:38
Kejadian 2:38
hasilnya Benar
Anehnya, kecepatannya dua kali lebih lambat dibandingkan menggunakan ToUpper, dan sampah generasi kedua juga dikumpulkan sebanyak 38 kali (saat melaksanakan pengumpulan sampah generasi kedua, pengumpulan sampah generasi pertama dan generasi nol akan dipaksa).
Namun ide menggunakan tabel Hash atau sejenisnya untuk mempercepat proses adalah ide yang sangat tepat, namun premisnya adalah tabel Hash itu sendiri dapat di-cache, misalnya:
Fungsi statis pribadi
{
HashSet
set kembali. Berisi;
}
Kemudian ubah metode utama menjadi:
Console.WriteLine("Gunakan ImplementByHashSet2");
hasil = MengukurKinerja(s =>
{
var f = ImplementByHashSet2(strs);
bool ret = salah;
untuk (int saya = 0; saya < 1000; saya++)
{
kembali = f(s);
}
kembali mundur;
}, "yZh", 1);
Console.WriteLine("hasilnya adalah " + hasil.ToString());
Konsol.ReadLine();
Mari kita lihat hasilnya:
Gunakan ImplementByHashSet2
6 ms
Kejadian 0:0
Kejadian 1:0
Kejadian 2:0
hasilnya Benar
Performanya meningkat drastis.
Lagi
Apa yang memperlambat c#/.net? Sederhananya: pembuatan objek yang tidak perlu, sinkronisasi yang tidak perlu, metode eksekusi loop yang tidak efisien (seperti refleksi yang dikritik oleh firelong, tetapi ms tidak mengizinkan Anda menggunakan Invoke dalam loop), penggunaan struktur data dan Algoritma yang tidak efisien (lihat pada kinerja luar biasa dari struktur tabel Hash yang serupa dalam kasus caching, dan Anda akan mengetahui perbedaannya)
Ambang batas rendah c#/.net memang membantu menarik lebih banyak pemrogram ke c#/.net sampai batas tertentu, tetapi juga mengurangi banyak tingkat kode seluruh program c#/.net, yang sangat mengesankan.
Terakhir, jangan lupa bahwa kinerja suatu sistem tidak ditentukan oleh bagian sistem yang berkinerja terbaik, tetapi oleh bagian sistem yang berkinerja terburuk. Dilengkapi dengan memori 16g, hard drive 100t, ditambah kartu grafis kelas atas, namun tanpa CPU 386, performa komputer ini adalah performa 386. Demikian pula, betapapun bagusnya C#/.net, jika keterampilan pemrogramnya buruk, kinerja program yang ditulis dengan sendirinya juga akan buruk.