Продуктивність FactoryFinder / поганий кешування


9

У мене досить великий додаток java ee з величезним classpath, що робить багато XML-обробки. В даний час я намагаюся пришвидшити деякі свої функції та знайти повільні шляхи коду за допомогою пробовідбірників.

Одне, що я помітив, - це те, що особливо частини нашого коду, у яких ми маємо дзвінки TransformerFactory.newInstance(...), відчайдушно повільні. Я простежив це до FactoryFinderметоду, findServiceProviderзавжди створюючи новий ServiceLoaderекземпляр. У ServiceLoader javadoc я знайшов таку примітку про кешування:

Постачальники розташовані та інстанціровані ліниво, тобто на вимогу. Сервісний навантажувач підтримує кеш постачальників, завантажених до цього часу. Кожне виклик методу ітератора повертає ітератор, який спочатку видає всі елементи кешу, в порядку інстанції, а потім ліниво знаходить і інстанціює всі інші постачальники, додаючи кожного з них у кеш по черзі. Кеш можна очистити за допомогою методу перезавантаження.

Все йде нормально. Це частина FactoryFinder#findServiceProviderметоду OpenJDK :

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 взагалі не використовується. Кожен виклик сканує класний шлях для запитуваного ServiceProvider.

Що я вже пробував:

  1. Я знаю, що ви можете встановити властивість системи, як, javax.xml.transform.TransformerFactoryщоб вказати конкретну реалізацію. Таким чином FactoryFinder не використовує процес ServiceLoader та його надшвидкий. На жаль, це властивість у форматі jvm і впливає на інші процеси Java, що працюють у моєму jvm. Наприклад, моя програма постачається з Saxon і повинна використовувати. com.saxonica.config.EnterpriseTransformerFactoryУ мене є ще одна програма, яка не постачається з Saxon. Як тільки я встановив властивість системи, інша моя програма не запускається, оскільки її немає com.saxonica.config.EnterpriseTransformerFactoryна її класі. Тож це не здається для мене варіантом.
  2. Я вже реконструював кожне місце, де TransformerFactory.newInstanceвикликається a , і кешую TransformerFactory. Але в моїх залежностях є різні місця, де я не можу переробити код.

Мої запитання: Чому FactoryFinder не повторно використовує ServiceLoader? Чи існує спосіб пришвидшити весь процес ServiceLoader, окрім використання системних властивостей? Чи не можна це змінити в JDK, щоб FactoryFinder повторно використовував екземпляр ServiceLoader? Також це не характерно для одного FactoryFinder. Ця поведінка однакова для всіх класів FactoryFinder в javax.xmlпакеті, який я розглянув досі.

Я використовую OpenJDK 8/11. Мої програми розміщені в екземплярі Tomcat 9.

Редагувати: надавати більше деталей

Ось стек викликів для одного виклику XMLInputFactory.newInstance: введіть тут опис зображення

Там, де використовується більшість ресурсів, знаходиться в ServiceLoaders$LazyIterator.hasNextService. Цей метод закликає getResourcesClassLoader прочитати META-INF/services/javax.xml.stream.XMLInputFactoryфайл. Один лише цей дзвінок займає приблизно 35 мс кожного разу.

Чи є спосіб доручити 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 мс звучить так, що тут є час доступу до дисків, і це вказує на проблему з кешуванням ОС.

Якщо на classpath є якісь записи в каталозі / поза jar, які можуть уповільнити ситуацію. Також якщо ресурс відсутній у першому місці, яке перевіряється.

ClassLoader.getResourceможе бути відмінено, якщо ви можете встановити завантажувач контекстного класу потоку через конфігурацію (я не торкався tomcat роками) або просто Thread.setContextClassLoader.


Схоже, це може спрацювати. Я рано чи пізно перегляну це. Дякую!
Вагнер Майкл

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 налаштовано значення за замовчуванням 5000 мс. Це підмануло мене під час тестування продуктивності, оскільки у мене було трохи більше 5 секунд між моїми запитами (переглядаючи графіки та інше). Ось чому всі мої запити в основному проходили без кешу і ZipFile.openщоразу викликали це важке .

Оскільки я не дуже досвідчений у налаштуваннях Tomcat, я ще не впевнений, що є правильним рішенням. Збільшення cacheTTL зберігає кеші довше, але не виправляє проблему в довгостроковій перспективі.

Підсумок

Я думаю, що насправді тут є два винуватці.

  1. Класи FactoryFinder не використовують повторно ServiceLoader. Можливо, є поважна причина, чому вони не використовують їх повторно - я не можу по-справжньому придумати одну.

  2. Tomcat виселяє кеші після встановленого часу для ресурсу веб-додатків (файли classpath - як ServiceLoaderконфігурація)

Поєднайте це з тим, що не визначили властивість системи для класу ServiceLoader, і ви отримаєте повільний виклик FactoryFinder кожні cacheTtlсекунди.

Наразі я можу жити зі збільшенням cacheTtl до більш тривалого часу. Я також міг би поглянути на пропозицію Тома Хоутінса про переоцінку, Classloader.getResourcesнавіть якщо я думаю, що це суворий спосіб позбутися цього вузького місця. Це, можливо, варто придивитись.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.