ประสิทธิภาพของ FactoryFinder / การแคชไม่ดี


9

ฉันมีแอปพลิเคชัน Java ee ค่อนข้างใหญ่ที่มี classpath ขนาดใหญ่ที่ทำการประมวลผล xml จำนวนมาก ขณะนี้ฉันกำลังพยายามเพิ่มความเร็วฟังก์ชั่นบางอย่างของฉันและค้นหาเส้นทางของรหัสที่ช้าโดยใช้เครื่องมือเก็บตัวอย่าง

สิ่งหนึ่งที่ฉันสังเกตเห็นคือโดยเฉพาะอย่างยิ่งส่วนต่าง ๆ ของรหัสของเราที่เรามีการเรียกเช่นTransformerFactory.newInstance(...)ช้ามาก ฉันติดตามสิ่งนี้เป็นFactoryFinderวิธีfindServiceProviderการสร้างServiceLoaderอินสแตนซ์ใหม่เสมอ ในServiceLoader javadocฉันพบบันทึกย่อต่อไปนี้เกี่ยวกับการแคช:

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

จนถึงตอนนี้ดีมาก นี่เป็นส่วนหนึ่งของFactoryFinder#findServiceProviderวิธีการOpenJDKs :

private static <T> T findServiceProvider(final Class<T> type)
        throws TransformerFactoryConfigurationError
    {
      try {
            return AccessController.doPrivileged(new PrivilegedAction<T>() {
                public T run() {
                    final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
                    final Iterator<T> iterator = serviceLoader.iterator();
                    if (iterator.hasNext()) {
                        return iterator.next();
                    } else {
                        return null;
                    }
                 }
            });
        } catch(ServiceConfigurationError e) {
            ...
        }
    }

สายทุกสายfindServiceProvider ServiceLoader.loadสิ่งนี้จะสร้างServiceLoader ใหม่ทุกครั้ง วิธีนี้ดูเหมือนว่าไม่มีการใช้กลไกการแคช ServiceLoaders เลย การโทรทุกครั้งจะสแกน classpath สำหรับ ServiceProvider ที่ร้องขอ

สิ่งที่ฉันได้ลองไปแล้ว:

  1. ฉันรู้ว่าคุณสามารถตั้งค่าคุณสมบัติของระบบที่ต้องการjavax.xml.transform.TransformerFactoryระบุการใช้งานเฉพาะ วิธีนี้ FactoryFinder ไม่ใช้กระบวนการ ServiceLoader และมันเร็วสุด น่าเศร้าที่นี่เป็นคุณสมบัติกว้าง jvm และมีผลต่อกระบวนการ java อื่น ๆ ที่ทำงานใน jvm ของฉัน ตัวอย่างเช่นแอปพลิเคชันของฉันมาพร้อมกับ Saxon และควรใช้com.saxonica.config.EnterpriseTransformerFactoryฉันมีแอปพลิเคชันอื่นที่ไม่ได้จัดส่งกับ Saxon ทันทีที่ฉันตั้งค่าคุณสมบัติระบบแอปพลิเคชันอื่นของฉันไม่สามารถเริ่มทำงานได้เนื่องจากไม่มีcom.saxonica.config.EnterpriseTransformerFactoryอยู่ใน classpath ดังนั้นนี่ดูเหมือนจะไม่ใช่ตัวเลือกสำหรับฉัน
  2. ฉันได้รับการปรับโครงสร้างใหม่แล้วทุกที่ที่TransformerFactory.newInstanceเรียกว่าและแคช TransformerFactory แต่มีหลายสถานที่ในการพึ่งพาของฉันที่ฉันไม่สามารถ refactor รหัส

คำถามของฉันคือ: เหตุใด FactoryFinder จึงไม่นำ ServiceLoader ไปใช้ซ้ำ มีวิธีใดบ้างที่จะเร่งกระบวนการทั้งหมดของ ServiceLoader นอกเหนือจากการใช้คุณสมบัติของระบบ สิ่งนี้ไม่สามารถเปลี่ยนแปลงได้ใน JDK เพื่อให้ FactoryFinder นำอินสแตนซ์ของ ServiceLoader กลับมาใช้ใหม่ได้หรือไม่ นอกจากนี้นี่ไม่เฉพาะกับ FactoryFinder เดียว บาฮาเวียร์นี้เหมือนกันสำหรับคลาส FactoryFinder ทั้งหมดในjavax.xmlแพ็คเกจที่ฉันได้ดูไปแล้ว

ฉันใช้ OpenJDK 8/11 แอปพลิเคชันของฉันถูกปรับใช้ในอินสแตนซ์ Tomcat 9

แก้ไข: ให้รายละเอียดเพิ่มเติม

นี่คือสแตกการเรียกสำหรับการเรียก XMLInputFactory.newInstance เดียว: ป้อนคำอธิบายรูปภาพที่นี่

ServiceLoaders$LazyIterator.hasNextServiceที่เป็นทรัพยากรส่วนใหญ่จะใช้อยู่ใน วิธีนี้เรียกร้องgetResourcesให้ ClassLoader อ่านMETA-INF/services/javax.xml.stream.XMLInputFactoryไฟล์ การโทรนั้นจะใช้เวลาประมาณ 35ms ต่อครั้ง

มีวิธีในการสั่งให้ Tomcat ทำการแคชไฟล์เหล่านี้ให้ดีขึ้นหรือไม่เพื่อให้สามารถทำงานได้เร็วขึ้น?


ฉันเห็นด้วยกับการประเมินของคุณของ FactoryFinder.java ดูเหมือนว่าควรทำการแคช ServiceLoader คุณได้ลองดาวน์โหลดแหล่ง openjdk แล้วสร้างมันขึ้นมา ฉันรู้ว่าเสียงเหมือนงานที่มีขนาดใหญ่ แต่อาจจะไม่ นอกจากนี้อาจเป็นการดีที่จะเขียนปัญหากับ FactoryFinder.java และดูว่ามีใครบางคนหยิบปัญหาขึ้นมาและเสนอวิธีแก้ไขปัญหาหรือไม่
djhallx

คุณลองตั้งค่าคุณสมบัติโดยใช้-Dแฟล็กเป็นTomcatกระบวนการของคุณหรือไม่? ตัวอย่างเช่น: -Djavax.xml.transform.TransformerFactory=<factory class>.ไม่ควรแทนที่คุณสมบัติของแอพอื่น โพสต์ของคุณได้รับการอธิบายอย่างดีและคุณอาจจะลองแล้ว แต่ฉันต้องการยืนยัน ดูวิธีการตั้งค่าคุณสมบัติระบบ Javax.xml.transform.TransformerFactory , วิธีการตั้งค่า HeapMemory หรือ JVM ข้อโต้แย้งใน Tomcat
Michał Ziober

คำตอบ:


1

35 msดูเหมือนว่าจะมีเวลาในการเข้าถึงดิสก์และชี้ไปที่ปัญหาเกี่ยวกับการแคชระบบปฏิบัติการ

หากมีรายการไดเรกทอรี / ไม่ใช่ขวดใด ๆ บน classpath ที่สามารถทำให้ช้าลง นอกจากนี้หากทรัพยากรไม่ได้อยู่ที่ตำแหน่งแรกที่มีการตรวจสอบ

ClassLoader.getResourceสามารถแทนที่ถ้าคุณสามารถตั้งค่ากระบอกชั้นบริบทด้ายทั้งผ่านการตั้งค่า (ผมไม่ได้สัมผัสคราวสำหรับปี) Thread.setContextClassLoaderหรือเพียงแค่


ฟังดูเหมือนจะเป็นไปได้ ฉันจะดูที่นี้ไม่ช้าก็เร็ว ขอบคุณ!
Wagner Michael

1

ฉันสามารถแก้จุดบกพร่องนี้อีก 30 นาทีและดูว่า Tomcat ใช้การแคชทรัพยากรอย่างไร

โดยเฉพาะCachedResource.validateResources(ซึ่งสามารถพบได้ในกราฟไฟด้านบน) เป็นที่สนใจสำหรับฉัน มันจะกลับมาtrueถ้าCachedResourceยังคงถูกต้อง:

protected boolean validateResources(boolean useClassLoaderResources) {
        long now = System.currentTimeMillis();
        if (this.webResources == null) {
            ...
        }

        // TTL check here!!
        if (now < this.nextCheck) {
            return true;
        } else if (this.root.isPackedWarFile()) {
            this.nextCheck = this.ttl + now;
            return true;
        } else {
            return false;
        }
    }

ดูเหมือนว่าCachedResourceจริง ๆ แล้วมีเวลาอยู่ (ttl) จริง ๆ แล้วมีวิธีใน Tomcat เพื่อกำหนดค่าcacheTtlแต่คุณสามารถเพิ่มค่านี้ได้เท่านั้น การกำหนดค่าการแคชทรัพยากรไม่ยืดหยุ่นอย่างง่ายดาย

ดังนั้น Tomcat ของฉันมีค่าเริ่มต้นที่กำหนดไว้ที่ 5,000 ms สิ่งนี้หลอกให้ฉันขณะทำการทดสอบประสิทธิภาพเพราะฉันมีเวลามากกว่า 5 วินาทีระหว่างคำขอของฉัน (ดูกราฟและเนื้อหา) นั่นเป็นเหตุผลที่คำขอทั้งหมดของฉันโดยทั่วไปทำงานโดยไม่มีแคชและเรียกใช้งานหนักZipFile.openทุกครั้ง

ดังนั้นฉันไม่ค่อยมีประสบการณ์กับการกำหนดค่า Tomcat ฉันยังไม่แน่ใจว่าอะไรคือทางออกที่ถูกต้องที่นี่ การเพิ่ม cacheTTL จะทำให้แคชยาวขึ้น แต่ไม่สามารถแก้ไขปัญหาได้ในระยะยาว

สรุป

ฉันคิดว่ามีผู้ร้ายสองคนที่นี่

  1. คลาส FactoryFinder ไม่ได้ใช้ ServiceLoader ซ้ำ อาจมีเหตุผลที่ถูกต้องว่าทำไมพวกเขาไม่นำมาใช้ซ้ำ - ฉันไม่สามารถคิดได้อย่างใดอย่างหนึ่ง

  2. Tomcat evicting caches หลังจากเวลาที่กำหนดสำหรับทรัพยากรแอปพลิเคชันเว็บ (ไฟล์ใน classpath - เช่นการServiceLoaderกำหนดค่า)

รวมสิ่งนี้เข้ากับการไม่ได้กำหนดคุณสมบัติระบบสำหรับคลาส ServiceLoader และคุณจะได้รับการเรียกใช้ FactoryFinder ที่ช้าทุก ๆcacheTtlวินาที

ตอนนี้ฉันสามารถอยู่กับการเพิ่ม cacheTtl เป็นเวลานาน ฉันอาจจะดูข้อเสนอแนะของ Tom Hawtins ในการเอาชนะClassloader.getResourcesแม้ว่าฉันคิดว่านี่เป็นวิธีที่รุนแรงในการกำจัดคอขวดการแสดงนี้ มันอาจจะคุ้มค่าที่จะดู

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