RegexOptions.Compiled ทำงานอย่างไร


169

เกิดอะไรขึ้นเบื้องหลังเมื่อคุณทำเครื่องหมายนิพจน์ปกติให้เป็นคอมไพล์แล้ว สิ่งนี้เปรียบเทียบ / แตกต่างจากนิพจน์ทั่วไปที่แคชได้อย่างไร

การใช้ข้อมูลนี้คุณจะทราบได้อย่างไรว่าต้นทุนการคำนวณมีน้อยมากเมื่อเทียบกับการเพิ่มประสิทธิภาพ


ทรัพยากรที่ดีสำหรับแนวปฏิบัติที่ดีที่สุดของ Regex: docs.microsoft.com/en-us/dotnet/standard/base-types/ …
CAD bloke

คำตอบ:


302

RegexOptions.Compiledสั่งให้เอ็นจินนิพจน์ทั่วไปรวบรวมนิพจน์ทั่วไปลงใน IL โดยใช้การสร้างรหัสแบบ Lightweight ( LCG ) รวบรวมเรื่องนี้เกิดขึ้นในระหว่างการก่อสร้างของวัตถุและหนักช้าลง ในทางกลับกันการแข่งขันโดยใช้การแสดงออกปกติจะเร็วขึ้น

หากคุณไม่ได้ระบุแฟล็กนี้นิพจน์ปกติของคุณจะถูกพิจารณาเป็น "ตีความ"

ใช้ตัวอย่างนี้:

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[] { "sam@sam.com", "sss@s", "sjg@ddd.com.au.au", "onelongemail@oneverylongemail.com" };

    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]);
        });

    }
}

จะทำการทดสอบ 4 ครั้งในนิพจน์ทั่วไป 3 แบบ ก่อนอื่นจะทดสอบการแข่งขันครั้งเดียวครั้งเดียว (รวบรวม vs ไม่รวบรวม) ที่สองเป็นการทดสอบการจับคู่ซ้ำที่ใช้ซ้ำการแสดงออกปกติเดียวกัน

ผลลัพธ์ในเครื่องของฉัน (รวบรวมในการเปิดตัวไม่มีการเชื่อมต่อดีบักเกอร์)

1000 แมตช์เดียว (สร้าง Regex, จับคู่และกำจัด)

ประเภท | แพลตฟอร์ม | จำนวนเล็กน้อย ตรวจสอบอีเมล์ง่าย ๆ ตรวจสอบอีเมลภายนอก
-------------------------------------------------- ----------------------------
ตีความ | x86 | 4 ms | 26 ms | 31 ms
ตีความ | x64 | 5 ms | 29 ms | 35 มิลลิวินาที
รวบรวม | x86 | 913 ms | 3775 ms | 4487 ms
รวบรวม | x64 | 3300 ms | 21985 ms | 22793 มิลลิวินาที

1,000,000 แมตช์ - นำวัตถุ Regex กลับมาใช้ใหม่

ประเภท | แพลตฟอร์ม | จำนวนเล็กน้อย ตรวจสอบอีเมล์ง่าย ๆ ตรวจสอบอีเมลภายนอก
-------------------------------------------------- ----------------------------
ตีความ | x86 | 422 ms | 461 ms | 2122 มิลลิวินาที
ตีความ | x64 | 436 ms | 463 ms | 2167 มิลลิวินาที
รวบรวม | x86 | 279 ms | 166 ms | 1268 มิลลิวินาที
รวบรวม | x64 | 281 ms | 176 ms | 1180 มิลลิวินาที

ผลลัพธ์เหล่านี้แสดงว่านิพจน์ทั่วไปที่คอมไพล์แล้วอาจเร็วขึ้นถึง60%สำหรับกรณีที่คุณนำRegexวัตถุกลับมาใช้ใหม่ อย่างไรก็ตามในบางกรณีอาจมีขนาดมากกว่า3 คำสั่งที่ช้ากว่าในการสร้าง

นอกจากนี้ยังแสดงให้เห็นว่า.NET รุ่น x64สามารถช้าลงได้5 ถึง 6 เท่าเมื่อมีการรวบรวมนิพจน์ทั่วไป


ข้อเสนอแนะคือการใช้รุ่นรวบรวมในกรณีที่อย่างใดอย่างหนึ่ง

  1. คุณไม่สนใจค่าใช้จ่ายในการเริ่มต้นวัตถุและต้องการเพิ่มประสิทธิภาพการทำงานพิเศษ (โปรดทราบว่าเรากำลังพูดถึงเศษส่วนของมิลลิวินาทีที่นี่)
  2. คุณสนใจค่าใช้จ่ายในการเริ่มต้นเล็กน้อย แต่จะนำวัตถุ Regex กลับมาใช้ซ้ำหลายครั้งซึ่งจะชดเชยให้ในระหว่างวงจรชีวิตแอปพลิเคชันของคุณ

ประแจในการทำงานแคช Regex

เอ็นจิ้นนิพจน์ทั่วไปประกอบด้วยแคช LRU ที่เก็บนิพจน์ปกติ 15 ครั้งล่าสุดที่ทดสอบโดยใช้วิธีการคงที่ในRegexคลาส

ตัวอย่างเช่นRegex.Replace, Regex.Matchฯลฯ .. การใช้งานทั้งหมดแคช Regex

Regex.CacheSizeขนาดของแคชสามารถเพิ่มขึ้นโดยการตั้งค่า ยอมรับการเปลี่ยนแปลงขนาดตลอดเวลาในระหว่างวงจรชีวิตของแอปพลิเคชัน

นิพจน์ทั่วไปใหม่ถูกแคชไว้โดยผู้ช่วยเหลือคงที่ในคลาส Regex เท่านั้น ถ้าคุณสร้างวัตถุของคุณแคชมีการตรวจสอบ (สำหรับการใช้ซ้ำและชน) แต่การแสดงออกปกติคุณสร้างจะไม่ได้ผนวกเข้ากับแคช

แคชนี้เป็นแคช LRU เล็ก ๆ น้อย ๆซึ่งจะถูกนำไปใช้งานโดยใช้รายการลิงก์คู่ที่เรียบง่าย หากคุณเพิ่มขึ้นเป็น 5,000 และใช้การโทรที่แตกต่างกัน 5,000 รายการบนผู้ช่วยคงที่การสร้างนิพจน์ทั่วไปทุกครั้งจะรวบรวมข้อมูล 5000 รายการเพื่อดูว่าเคยแคชไว้ก่อนหน้านี้หรือไม่ มีล็อกอยู่รอบ ๆ เช็คดังนั้นการตรวจสอบสามารถลดความขนานและแนะนำการบล็อกเธรด

จำนวนถูกตั้งค่าค่อนข้างต่ำเพื่อป้องกันตัวเองจากกรณีเช่นนี้แม้ว่าในบางกรณีคุณอาจไม่มีทางเลือกนอกจากเพิ่ม

คำแนะนำที่แข็งแกร่งของฉันจะไม่ผ่านRegexOptions.Compiledตัวเลือกไปยังผู้ช่วยคงที่

ตัวอย่างเช่น:

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

สาเหตุที่คุณกำลังเสี่ยงอย่างมากที่จะพลาดแคชของ LRU ซึ่งจะทำให้คอมไพล์มีราคาแพงมาก นอกจากนี้คุณไม่ทราบว่าไลบรารีที่คุณใช้ทำนั้นมีความสามารถเพียงเล็กน้อยในการควบคุมหรือทำนายขนาดแคชที่ดีที่สุดเท่าที่จะเป็นไปได้

ดูเพิ่มเติม: บล็อกทีม BCL


หมายเหตุ : สิ่งนี้เกี่ยวข้องกับ. NET 2.0 และ. NET 4.0 มีการเปลี่ยนแปลงที่คาดหวังบางอย่างใน 4.5 ที่อาจทำให้สิ่งนี้ได้รับการแก้ไข


11
คำตอบที่ดี สำหรับวัตถุประสงค์ของตัวเองผมมักจะใช้Compiledในโค้ดของเว็บไซต์ที่ฉันจริงเก็บคงที่ (โปรแกรมประยุกต์กว้าง) Regexวัตถุ ดังนั้นRegexจะต้องมีการสร้างเพียงครั้งเดียวเมื่อ IIS เริ่มต้นแอปพลิเคชันแล้วนำกลับมาใช้ใหม่นับพันครั้ง สิ่งนี้ใช้ได้ดีตราบใดที่แอปพลิเคชันไม่รีสตาร์ทบ่อยครั้ง
Steve Wortham

W00! ข้อมูลนี้ช่วยให้ฉันเร่งกระบวนการของฉันให้สูงขึ้นจาก 8-13 ชั่วโมงเป็น ~ 30 นาที ขอบคุณ!
Robert Christ

3
คำตอบที่ดี Sam คุณสามารถอัปเดตเกี่ยวกับสิ่งที่เปลี่ยนแปลงในเวอร์ชัน> 4.5 ได้ไหม (ฉันรู้ว่าคุณเปลี่ยนสแต็กไปพักหนึ่งแล้ว ... )
gdoron ให้การสนับสนุนโมนิก้า

@gdoronissupportingMonica มีการปรับปรุงประสิทธิภาพของ Regex ใน NET 5.0 ฉันเห็นโพสต์บล็อกสำหรับสิ่งนี้ คุณสามารถตรวจสอบได้ที่นี่
kapozade

42

รายการในบล็อกทีม BCL นี้ให้ภาพรวมที่ดี: " ประสิทธิภาพการแสดงผลปกติ "

กล่าวโดยสรุปมีสามประเภทของ regex (แต่ละการดำเนินการเร็วกว่ารุ่นก่อนหน้า):

  1. ตีความ

    รวดเร็วในการสร้างได้ทันทีช้าในการดำเนินการ

  2. รวบรวม (คนที่คุณดูเหมือนจะถามเกี่ยวกับ)

    ช้าลงเพื่อสร้างได้ทันทีดำเนินการอย่างรวดเร็ว (ดีสำหรับการประมวลผลในลูป)

  3. ก่อนรวบรวม

    สร้าง ณ เวลารวบรวมแอปของคุณ (ไม่มีการลงโทษการสร้างเวลาทำงาน) และรวดเร็วในการดำเนินการ

ดังนั้นหากคุณตั้งใจจะดำเนินการ regex เพียงครั้งเดียวหรือในส่วนที่ไม่สำคัญต่อประสิทธิภาพการทำงานของแอพของคุณ (เช่นการตรวจสอบการป้อนข้อมูลของผู้ใช้) คุณก็ใช้ได้กับตัวเลือก 1

หากคุณต้องการรัน regex ในการวนซ้ำ (เช่นการแยกไฟล์แบบทีละบรรทัด) คุณควรเลือกตัวเลือกที่ 2

หากคุณมี regexes มากมายที่จะไม่เปลี่ยนแปลงสำหรับแอปของคุณและมีการใช้อย่างเข้มข้นคุณสามารถไปที่ตัวเลือก 3


1
จำนวน 3 CompileModuleอาจจะทำง่ายผ่านโรสลินที่กำหนดเอง ประณามฉันต้องมองลึกลงไปใน plattform ใหม่
Christian Gollhardt

9

ควรสังเกตว่าประสิทธิภาพของนิพจน์ทั่วไปตั้งแต่. NET 2.0 ได้รับการปรับปรุงด้วย MRU cache ของนิพจน์ทั่วไปที่ไม่ได้คอมไพล์ รหัสไลบรารี Regex จะไม่ตีความการแสดงออกปกติที่ไม่ได้รวบรวมซ้ำทุกครั้งอีกต่อไป

ดังนั้นจึงมีความเป็นไปได้สูงที่จะมีการลงโทษด้วยการคอมไพล์และการแสดงออกปกติ นอกเหนือจากเวลาโหลดช้าระบบยังใช้หน่วยความจำเพิ่มเติมเพื่อรวบรวมนิพจน์ทั่วไปกับ opcodes

โดยพื้นฐานแล้วคำแนะนำในปัจจุบันไม่ได้รวบรวมการแสดงออกปกติหรือรวบรวมพวกเขาล่วงหน้าเพื่อการชุมนุมที่แยกต่างหาก

Ref: BCL Team Blog ประสิทธิภาพการแสดงผลปกติ [David Gutierrez]



0

ฉันหวังว่ารหัสด้านล่างจะช่วยให้คุณเข้าใจแนวคิดของฟังก์ชั่น re.compile

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)

ขอบคุณสำหรับคำตอบ แต่รหัสของคุณเป็นภาษาPython คำถามเกี่ยวกับ Microsoft .NET Framework RegexOptionsตัวเลือกที่คอมไพล์แล้ว คุณสามารถดูแท็ก[ .net ]ด้านล่างคำถาม
stomy

ใช่แล้ว! ขอบคุณ stomy
Daniel Muthupandi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.