Производительность разрешения выражений EL Value - PullRequest
9 голосов
/ 05 августа 2010

У меня есть приложение JSF2, которое отображает большую таблицу со сложным содержимым.К сожалению, каждый запрос обрабатывается до 6 секунд.Используя простой отладочный вывод внутри слушателя фазы, я мог видеть, что потеря производительности распределяется равномерно по всем фазам, которые обрабатывают дерево компонентов.Поэтому я запустил профилировщик, чтобы посмотреть, что происходит, и обнаружил, что более 300.000 ValueExpressions оцениваются во время одного простого запроса.

Они разрешаются в действительно простые методы получения без какой-либо логики, поэтому проблема заключается не в выполнениикод этих выражений, но синтаксический анализ строки выражения и вызов метода получения.Это приводит к нескольким вопросам:

1.) Есть ли способ ускорить разрешение выражений метода.Может быть, скрытый флаг «включить кэширование» или что-то в этом роде.

2.) Кажется, что большинство выражений оцениваются не внутри фазы ответа рендеринга, где они действительно необходимы, а во время других фаз.Кажется, нет необходимости разрешать, например, styleClass во время любой другой фазы, кроме фазы рендеринга.Могу ли я предотвратить это?

3.) Конечно, сведение к минимуму количества выражений EL на моей странице Facelets должно помочь повысить производительность, но, похоже, я не могу этого сделать: Многие атрибуты (например, * 1010).* пример, упомянутый выше) фактически зависит от строки таблицы, но может быть задан только для столбца.Таким образом, имея 10 столбцов, каждое выражение вычисляется слишком часто.Я видел примеры, когда атрибут rowClasses таблицы используется для условного стиля строк, но поскольку таблица сортируется, это не сработает без использования моего собственного механизма сортировки.Есть ли лучший способ реализовать это?

4.) Еще один простой вопрос: есть ли способ кэшировать переменные в дереве компонентов (точно так же, как ui:repeat обеспечивает доступ к содержимому списка и разрешаетвыражение для получения списка только один раз, но только для одной переменной)?

Большое спасибо за все ответы и подсказки!

РЕДАКТИРОВАТЬ:

После дальнейшего изучения я обнаружил, что для каждого rendered=#{someExpression} выражение оценивается 6 раз в строке непосредственно на этапе ответа рендеринга.Я знаю, что JSF может вызывать моих получателей более одного раза, но я думал, что это произойдет, потому что они могут быть вызваны внутри каждой фазы.Во время рендеринга эти значения не должны изменяться, поэтому я предполагаю, что они могут быть кэшированы.

Шагая по коду в отладчике, он выглядит как javax.faces.component.ComponentStateHelper (который появляется в каждой из трассировок стека, ведущих коцененный вызов метода) предоставляет карту для выполнения именно такого рода кэширования.Однако, похоже, это не работает так, как я ожидаю, и всегда переоценивает выражение ...

Ответы [ 5 ]

2 голосов
/ 10 октября 2013

Я знаю, что этот вид довольно старый, но я хочу добавить, что эта проблема была решена реализацией MyFaces.Это задокументировано в их вики: https://cwiki.apache.org/confluence/display/MYFACES/Cache+EL+Expressions

1 голос
/ 30 декабря 2011

После нескольких часов отладки я решил улучшить ComponentStateHelper, который является частью реализации Mojarra JSF2.Я взял последнюю версию 2.1.4 https://maven.java.net/content/repositories/releases/org/glassfish/javax.faces/2.1.4/javax.faces-2.1.4-sources.jar

Основная идея - сохранить результаты выражений EL, оцененных на каждом этапе.Я по-прежнему предполагаю, что результат выражения EL должен быть одинаковым в течение одной фазы.

Изменения в классе javax.faces.component.ComponentStateHelper:

class ComponentStateHelper implements StateHelper , TransientStateHelper {    

    ...

    // Own cache for method public Object eval(Serializable key, Object defaultValue) {
    int lastPhaseId = -1; // Last cached phase
    private Map<Serializable, Object> evalCache;

    ...
    public ComponentStateHelper(UIComponent component) {

        ... 
        // Instantiate own cache
        this.evalCache = new HashMap<Serializable, Object>();
    }

    ...

    /**
     * @see StateHelper#eval(java.io.Serializable, Object)
     */
    public Object eval(Serializable key, Object defaultValue) {
        Object retVal = get(key);        
        if (retVal == null) { 
            // Value evaluated and returned within one phase should be hopefully still same
            int currentPhaseId = FacesContext.getCurrentInstance().getCurrentPhaseId().getOrdinal();
            if(lastPhaseId < currentPhaseId) {
                // Probably stale cache, so clear it to get fresh results
                // in current phase
                evalCache.clear();
                lastPhaseId = currentPhaseId;
            }
            retVal = evalCache.get(key);     

            if(retVal == null) { 
                ValueExpression ve = component.getValueExpression(key.toString());
                if (ve != null) {
                    retVal = ve.getValue(component.getFacesContext().getELContext());
                }
            }
            // Remember returned value in own cache
            evalCache.put(key, retVal);         
        } 
        return ((retVal != null) ? retVal : defaultValue);
    }

    ...    
}

Это улучшение представляется функциональным иколичество обращений к моему управляемому бину резко сократилось, особенно обработано геттеров, которые были вызваны несколько раз для одного и того же компонента.

Возможно, я не понимаю, какие катастрофические последствия может вызвать это улучшение.Но если это выполнимо, мне интересно, почему ребята из JSF не использовали этот тип кэширования.

* РЕДАКТИРОВАТЬ: Это решение не может быть использовано, так как оно имеет проблемы в сочетании с DataModel !!!*

1 голос
/ 06 августа 2010

Если вы используете эталонную реализацию mojarra на glassfish, вы можете попробовать ночную сборку, как упомянуто в этом блоге от Ed Burns . Разработчики oracle adf внесли некоторые улучшения в производительность, связанные с оценкой выражений el.

Не уверен, что это связано, но вы также можете попытаться отключить частичное сохранение состояния, установив для параметра init javax.faces.PARTIAL_STATE_SAVING значение false.

1 голос
/ 30 декабря 2011

Я страдаю от повторяющихся вызовов геттеров на управляемых компонентах при использовании составного компонента, особенно обработанный метод получения вызывается миллион раз.Я не знаю, используете ли вы их тоже, но я хотел бы узнать ваше мнение о моем решении для кэширования.

Мне удалось с некоторыми особенностями кэширования, когда я следовал подсказке BalusC о компоненте поддержки в вопросе Привязка экземпляра управляемого компонента к составному компоненту .

Составной компонент:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:composite="http://java.sun.com/jsf/composite"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">

<!-- INTERFACE -->
<composite:interface componentType="fieldComponentType">
    <composite:attribute name="id" type="java.lang.String" required="true" />
    <composite:attribute name="label" type="java.lang.String" required="true"/>
    <composite:attribute name="toBeRendered" type="java.lang.Boolean" required="true" />
    <composite:attribute name="currentValue" required="true" />
</composite:interface>

<!-- IMPLEMENTATION -->
<composite:implementation>
    <h:panelGrid rendered="#{cc._toBeRendered}" columns="3">
        <h:outputText value="#{cc._label}:"/>&nbsp;
        <h:inputText id="#{cc.attrs.id}" rendered="#{cc._toBeRendered}"
            value="#{cc.attrs.currentValue}" /> 
    </h:panelGrid>
</composite:implementation>
</html>    

Компонент поддержки, который обеспечивает кэширование в течение одной фазы:

package cz.kamosh;

import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;

@FacesComponent(value = "fieldComponentType")
public final class FieldComponentType extends UIComponentBase implements NamingContainer {

    static class Setting {
        String label;
        Boolean toBeRendered;

        @Override
        public String toString() {
            return "Setting [label=" + label + ", toBeRendered=" + toBeRendered
                    + "]";
        }       
    }

    int lastPhaseId = -1;

    Setting currentSetting = null;

    public FieldComponentType() {
        System.out.println("Constructor FieldComponentType");
    }   

    @Override
    public String getFamily() {
        return "javax.faces.NamingContainer";
    }

    // Must be named with prefix _, otherwise infinite loop occurs
    public String get_label() {
        Setting setting = getSetting();
        if (setting.label == null) {
            setting.label = (String) getAttributes().get("label");
        }
        return setting.label;
    }

    // Must be named with prefix _, otherwise infinite loop occurs
    public boolean is_toBeRendered() {
        Setting setting = getSetting();
        if (setting.toBeRendered == null) {
            setting.toBeRendered = (Boolean) getAttributes().get("toBeRendered");
        }
        return setting.toBeRendered;
    }

    private Setting getSetting() {
        int phaseId = FacesContext.getCurrentInstance().getCurrentPhaseId()
                .getOrdinal();

        if (currentSetting == null || phaseId > lastPhaseId) {
            currentSetting = new Setting();
            lastPhaseId = phaseId;
        }
        return currentSetting;
    }

}

Страница тестирования:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:util="http://java.sun.com/jsf/composite/components">

<h:head>
    <title>Testing page</title>
</h:head>
<h:body>

    <h:form>
        <h:panelGrid>           
            <util:fieldComponent id="id3"
                label="#{testingBean.label}"
                toBeRendered="#{testingBean.toBeRendered}"
                currentValue="#{testingBean.myValue}" />

        </h:panelGrid>
        <h:commandButton value="Do something" actionListener="#{testingBean.doSomething}" />
    </h:form>

</h:body>
</html>

Управляемый компонент:

package cz.kamosh;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ViewScoped
@ManagedBean(name="testingBean")
public class TestingBean {

    private String myValue;

    public String getMyValue() {
        System.out.printf("getMyValue: %1$s\n", myValue);
        return myValue;
    }

    public void setMyValue(String myValue) {
        System.out.printf("setMyValue: %1$s\n", myValue);
        this.myValue = myValue;
    }

    public void doSomething() {
        System.out.printf("Do something, myValue: %1$s\n", this.myValue);
    }

    public String getLabel() {
        System.out.printf("getLabel\n");
        return "My value lbl";
    }

    public boolean isToBeRendered() {
        System.out.printf("isToBeRendered\n");
        return true;
    }

}

После профилированияиспользование jvisualvm 26 вызовов com.sun.faces.facelets.el.TagValueExpression.getValue уменьшилось с 26% до 16% от общего времени, потраченного на один полный запрос 'run' (по сравнению с составным компонентом, который не использует componentType = "fieldComponentType "- источники, не включенные в этот ответ).

Но в любом случае издержки самой JSF2-фреймворка стоят около 80% времени по сравнению с 20%, потраченными на мой код, даже если я вызываю какую-то базу данныхвыборки (это мой опыт из нашего производственного кода).И я считаю, что эти издержки довольно большие: - (

@ FR Итак, вы упомянули, что «Во время рендеринга значения не должны изменяться, поэтому я предполагаю, что они могут быть кэшированы». С этой точки зрения я могу позволить себе кэшированиеЗначения тегов в каждой фазе, я прав?

1 голос
/ 05 августа 2010

1.) Есть ли способ ускорить разрешение выражений метода.Может быть, скрытый флаг «включить кэширование» или что-то в этом роде.

Никто не приходит в голову.

2.) Кажется, что большинство выражений вычисляются не внутри рендерафаза реагирования, где они действительно необходимы, но на других фазах.Кажется, нет необходимости разрешать, например, styleClass во время любой другой фазы, кроме фазы рендеринга.Могу ли я предотвратить это?

Насколько я знаю, этого не произойдет.Единственные, которые могут / должны быть разрешены до ответа рендеринга, это rendered, required, disabled, readonly и value.

3.) Конечно, сведение к минимумуколичество выражений EL на моей странице Facelets должно помочь повысить производительность, но, похоже, я не могу этого сделать: многие атрибуты (например, пример styleClass, упомянутый выше) фактически зависят от строки таблицы, но могут быть установлены только для столбца.,Таким образом, имея 10 столбцов, каждое выражение вычисляется слишком часто.Я видел примеры, где атрибут rowClasses таблицы используется для условного стиля строк, но поскольку таблица сортируется, это не сработает без использования моего собственного механизма сортировки.Есть ли лучший способ реализовать это?

Вы могли бы передать работу по стилю умной части / комбинации JS / CSS.

4.) Еще одинпростой вопрос: есть ли способ кешировать переменные в дереве компонентов (точно так же как ui: repeat обеспечивает доступ к содержимому списка и разрешает выражение, чтобы получить список только один раз, но только для одной переменной)?

Использовать JSTL <c:set>.Я не уверен, как это все-таки повлияет, но тогда вы в основном переносите проблему куда-то еще.#{variableName} все равно будет стоить найти его в любой из областей.Вы также можете явно назвать область действия при доступе к переменной.Например, #{sessionScope.beanname}, который должен пропустить ненужное сканирование на странице и запросить области.

...