Bagaimana cara RegexOptions.Compiled bekerja?

169

Apa yang terjadi di balik layar ketika Anda menandai ekspresi reguler sebagai yang akan dikompilasi? Bagaimana ini membandingkan / berbeda dari ekspresi reguler di-cache?

Dengan menggunakan informasi ini, bagaimana Anda menentukan kapan biaya perhitungan dapat diabaikan dibandingkan dengan peningkatan kinerja?

Bob
sumber
sumber yang bagus tentang praktik terbaik Regex: docs.microsoft.com/en-us/dotnet/standard/base-types/…
CAD bloke

Jawaban:

302

RegexOptions.Compiledmenginstruksikan mesin ekspresi reguler untuk mengkompilasi ekspresi ekspresi reguler ke IL menggunakan generasi kode ringan ( LCG ). Kompilasi ini terjadi selama konstruksi objek dan sangat memperlambatnya. Pada gilirannya, kecocokan menggunakan ekspresi reguler lebih cepat.

Jika Anda tidak menentukan tanda ini, ekspresi reguler Anda dianggap "ditafsirkan".

Ambil contoh ini:

public static void TimeAction(string description, int times, Action func)
{
    // warmup
    func();

    var watch = new Stopwatch();
    watch.Start();
    for (int i = 0; i < times; i++)
    {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}

static void Main(string[] args)
{
    var simple = "^\\d+$";
    var medium = @"^((to|from)\W)?(?<url>http://[\w\.:]+)/questions/(?<questionId>\d+)(/(\w|-)*)?(/(?<answerId>\d+))?";
    var complex = @"^(([^<>()[\]\\.,;:\s@""]+"
      + @"(\.[^<>()[\]\\.,;:\s@""]+)*)|("".+""))@"
      + @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
      + @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+"
      + @"[a-zA-Z]{2,}))$";


    string[] numbers = new string[] {"1","two", "8378373", "38737", "3873783z"};
    string[] emails = new string[] { "[email protected]", "sss@s", "[email protected]", "[email protected]" };

    foreach (var item in new[] {
        new {Pattern = simple, Matches = numbers, Name = "Simple number match"},
        new {Pattern = medium, Matches = emails, Name = "Simple email match"},
        new {Pattern = complex, Matches = emails, Name = "Complex email match"}
    })
    {
        int i = 0;
        Regex regex;

        TimeAction(item.Name + " interpreted uncached single match (x1000)", 1000, () =>
        {
            regex = new Regex(item.Pattern);
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        i = 0;
        TimeAction(item.Name + " compiled uncached single match (x1000)", 1000, () =>
        {
            regex = new Regex(item.Pattern, RegexOptions.Compiled);
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        regex = new Regex(item.Pattern);
        i = 0;
        TimeAction(item.Name + " prepared interpreted match (x1000000)", 1000000, () =>
        {
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

        regex = new Regex(item.Pattern, RegexOptions.Compiled);
        i = 0;
        TimeAction(item.Name + " prepared compiled match (x1000000)", 1000000, () =>
        {
            regex.Match(item.Matches[i++ % item.Matches.Length]);
        });

    }
}

Ia melakukan 4 tes pada 3 ekspresi reguler yang berbeda. Pertama menguji satu pertandingan sekali (dikompilasi vs tidak dikompilasi). Kedua, tes ini mengulangi kecocokan yang menggunakan kembali persamaan reguler yang sama.

Hasil di mesin saya (dikompilasi dalam rilis, tidak ada debugger terpasang)

1000 pertandingan tunggal (buat Regex, Cocokkan dan buang)

Ketik | Platform | Nomor Trivial | Pemeriksaan Email Sederhana | Pemeriksaan Email Ekst
-------------------------------------------------- ----------------------------
Diterjemahkan | x86 | 4 ms | 26 ms | 31 md
Diterjemahkan | x64 | 5 ms | 29 ms | 35 ms
Disusun | x86 | 913 ms | 3775 ms | 4487 ms
Disusun | x64 | 3300 ms | 21985 ms | 22793 ms

1.000.000 kecocokan - menggunakan kembali objek Regex

Ketik | Platform | Nomor Trivial | Pemeriksaan Email Sederhana | Pemeriksaan Email Ekst
-------------------------------------------------- ----------------------------
Diterjemahkan | x86 | 422 ms | 461 ms | 2122 ms
Diterjemahkan | x64 | 436 ms | 463 ms | 2167 ms
Disusun | x86 | 279 ms | 166 ms | 1268 ms
Disusun | x64 | 281 ms | 176 ms | 1180 ms

Hasil ini menunjukkan bahwa ekspresi reguler yang dikompilasi dapat mencapai 60% lebih cepat untuk kasus di mana Anda menggunakan kembali Regexobjek. Namun dalam beberapa kasus bisa lebih dari 3 urutan besarnya lebih lambat untuk dibangun.

Ini juga menunjukkan bahwa versi .NET x64 bisa 5 sampai 6 kali lebih lambat ketika datang ke kompilasi ekspresi reguler.


Rekomendasi akan menggunakan versi yang dikompilasi dalam kasus di mana baik

  1. Anda tidak peduli dengan biaya inisialisasi objek dan memerlukan peningkatan kinerja ekstra. (perhatikan kita berbicara pecahan satu milidetik di sini)
  2. Anda sedikit peduli tentang biaya inisialisasi, tetapi menggunakan kembali objek Regex berkali-kali sehingga akan menggantinya selama siklus hidup aplikasi Anda.

Spanner dalam karya, cache regex

Mesin ekspresi reguler berisi cache LRU yang menampung 15 ekspresi reguler terakhir yang diuji menggunakan metode statis di Regexkelas.

Sebagai contoh: Regex.Replace, Regex.Matchdll .. semua menggunakan cache Regex.

Ukuran cache dapat ditingkatkan dengan pengaturan Regex.CacheSize. Ini menerima perubahan ukuran setiap saat selama siklus hidup aplikasi Anda.

Ekspresi reguler baru hanya di-cache oleh pembantu statis di kelas Regex. Namun, jika Anda membuat objek, cache diperiksa (untuk digunakan kembali dan dihantam), ekspresi reguler yang Anda buat tidak ditambahkan ke cache .

Cache ini adalah cache LRU yang sepele , ini diimplementasikan menggunakan daftar ditautkan sederhana. Jika Anda meningkatkannya menjadi 5.000, dan menggunakan 5.000 panggilan berbeda pada pembantu statis, setiap konstruksi ekspresi reguler akan merayapi 5.000 entri untuk melihat apakah sebelumnya telah di-cache. Ada kunci di sekitar cek, sehingga pemeriksaan dapat mengurangi paralelisme dan memperkenalkan pemblokiran ulir.

Jumlahnya diatur cukup rendah untuk melindungi diri dari kasus-kasus seperti ini, meskipun dalam beberapa kasus Anda mungkin tidak punya pilihan selain menambahnya.

Rekomendasi kuat saya tidak akan pernah meneruskan RegexOptions.Compiledopsi ke pembantu statis.

Sebagai contoh:

\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)

Alasannya adalah Anda berisiko besar kehilangan cache LRU yang akan memicu kompilasi yang sangat mahal . Selain itu, Anda tidak tahu apa yang perpustakaan Anda tergantung pada melakukan, sehingga memiliki sedikit kemampuan untuk kontrol atau memprediksi terbaik ukuran cache.

Lihat juga: blog tim BCL


Catatan : ini relevan untuk .NET 2.0 dan .NET 4.0. Ada beberapa perubahan yang diharapkan pada 4,5 yang dapat menyebabkan ini direvisi.

Sam Saffron
sumber
11
Jawaban yang bagus Untuk keperluan saya sendiri, saya sering menggunakan Compiledkode situs web di mana saya sebenarnya menyimpan objek statis (aplikasi-lebar) Regex. Jadi Regexsatu - satunya harus dibangun sekali ketika IIS memulai aplikasi, dan kemudian digunakan kembali ribuan kali. Ini berfungsi dengan baik selama aplikasi tidak sering restart.
Steve Wortham
W00! Informasi ini membantu saya mempercepat proses saya dari 8-13 jam, hingga ~ 30 menit. Terima kasih!
Robert Christ
3
Jawaban bagus Sam, dapatkah Anda memperbarui tentang apa yang telah berubah di versi> 4.5? (Saya tahu Anda mengubah tumpukan Anda beberapa waktu lalu ...)
gdoron mendukung Monica
@gdoronissupportingMonica Ada beberapa peningkatan kinerja Regex pada NET 5.0. Saya melihat posting blog untuk ini. Anda dapat memeriksanya di sini
kapozade
42

Entri ini di Blog Tim BCL memberikan gambaran yang bagus: " Performa Ekspresi Reguler ".

Singkatnya, ada tiga jenis regex (masing-masing menjalankan lebih cepat dari yang sebelumnya):

  1. ditafsirkan

    cepat untuk membuat dengan cepat, lambat untuk dieksekusi

  2. dikompilasi (yang sepertinya Anda tanyakan)

    lebih lambat untuk membuat dengan cepat, cepat untuk mengeksekusi (baik untuk eksekusi dalam loop)

  3. pra-kompilasi

    buat pada waktu kompilasi aplikasi Anda (tidak ada penalti penciptaan run-time), cepat dijalankan

Jadi, jika Anda bermaksud menjalankan regex hanya sekali, atau di bagian non-kinerja-kritis dari aplikasi Anda (yaitu validasi input pengguna), Anda setuju dengan opsi 1.

Jika Anda bermaksud menjalankan regex dalam satu lingkaran (yaitu penguraian file baris demi baris), Anda harus memilih opsi 2.

Jika Anda memiliki banyak regex yang tidak akan pernah berubah untuk aplikasi Anda dan digunakan secara intensif, Anda bisa menggunakan opsi 3.

Tomalak
sumber
1
Nomor 3 dapat dipermudah melalui roslyn khususCompileModule . Sial, saya perlu melihat lebih dalam ke plattform baru.
Christian Gollhardt
9

Perlu dicatat bahwa kinerja ekspresi reguler sejak .NET 2.0 telah ditingkatkan dengan cache MRU dari ekspresi reguler yang tidak dikompilasi. Kode pustaka Regex tidak lagi menginterpretasi ulang ekspresi reguler yang sama yang tidak dikompilasi setiap waktu.

Jadi ada potensi penalti kinerja yang lebih besar dengan ekspresi reguler terkompilasi dan on the fly. Selain waktu muat yang lebih lambat, sistem juga menggunakan lebih banyak memori untuk mengkompilasi ekspresi reguler ke opcode.

Pada dasarnya, saran saat ini adalah jangan mengkompilasi ekspresi reguler, atau mengkompilasi terlebih dahulu ke majelis terpisah.

Ref: Blog BCL Team Kinerja ekspresi reguler [David Gutierrez]

Robert Paulson
sumber
0

Saya harap kode di bawah ini akan membantu Anda memahami konsep fungsi kompilasi ulang

import re

x="""101 COM    Computers
205 MAT   Mathematics
189 ENG   English
222 SCI Science
333 TA  Tamil
5555 KA  Kannada
6666  TL  Telugu
777777 FR French
"""

#compile reg expression / successfully compiled regex can be used in any regex 
#functions    
find_subject_code=re.compile("\d+",re.M)
#using compiled regex in regex function way - 1
out=find_subject_code.findall(x)
print(out)
#using compiled regex in regex function way - 2
out=re.findall(find_numbers,x)
print(out)

#few more eg:
#find subject name
find_subjectnames=re.compile("(\w+$)",re.M) 
out=find_subjectnames.findall(x)
print(out)


#find subject SHORT name
find_subject_short_names=re.compile("[A-Z]{2,3}",re.M) 
out=find_subject_short_names.findall(x)
print(out)
Daniel Muthupandi
sumber
Terima kasih atas jawaban Anda tetapi kode Anda dalam bahasa Python . Pertanyaannya adalah tentang Microsoft .NET framework RegexOptions.Compiled option. Anda dapat melihat tag [ .net ] yang terlampir di bawah pertanyaan.
stomy
ohh ya! Terima kasih stomi
Daniel Muthupandi