Performance Improvement Prop: Cache PortReference.extendedTo
dward Dec 7, 2009 10:23 AMAs part of the work in ActionProcessingPipeline.processPipeline(),
...esb.message.format.xml.HeaderImpl.toString() is called, which calls
...esb.addressing.Call.toString(), which calls
...esb.addressing.eprs.InVMEpr.toString(), which calls
...esb.addressing.PortReference.extendedToString().
PortReference.extendedToString() does a substantial amount of StringBuilder appending, which surprisingly accounted for 10% of the total testing time [1].
The proposed fix is to cache the extendedToString() value, being very careful to clear the cached value if any of the fields which build that value get mutated. So change this:
public void setAddress(String address) { _address = address; } public void addExtension(PortReference.Extension extension) { _extensions.add(extension); } public void removeExtension (String tag, String value) { _extensions.remove(new Extension(tag, XMLUtil.JBOSSESB_PREFIX, XMLUtil.JBOSSESB_NAMESPACE_URI, value)); } public void addExtension(String tag, String prefix, String uri, String value) { _extensions.add(new Extension(tag, prefix, uri, value, Extension.REFERENCE_PROPERTIES)); } public void addExtension(String tag, String prefix, String uri, String value, int parent) { _extensions.add(new Extension(tag, prefix, uri, value, parent)); } public void addExtensions(final List<Extension> extensions) { _extensions.addAll(extensions); } public List<Extension> getAllExtensions() { return _extensions; } public void clearExtensions() { _extensions.clear(); } public String extendedToString() { StringBuilder addr = new StringBuilder("PortReference < ").append("<").append(XMLUtil.WSA_PREFIX).append(":Address ").append(_address + "/>"); Iterator extensions = getExtensions(); while (extensions.hasNext()) { Extension ext = (Extension) extensions.next(); addr.append(", <").append(XMLUtil.WSA_PREFIX).append(":") .append(XMLUtil.REFERENCE_PROPERTIES_TAG).append(" ").append(ext.getPrefix()) .append(":").append(ext.getTag()).append(" : ").append(ext.getValue()).append("/>"); } return addr.append(" >").toString(); }
to this:
public void setAddress(String address) { synchronized (_extendedToString_LOCK) { _address = address; _extendedToString_CACHE = null; } } public void addExtension(PortReference.Extension extension) { synchronized (_extendedToString_LOCK) { _extensions.add(extension); _extendedToString_CACHE = null; } } public void removeExtension (String tag, String value) { synchronized (_extendedToString_LOCK) { _extensions.remove(new Extension(tag, XMLUtil.JBOSSESB_PREFIX, XMLUtil.JBOSSESB_NAMESPACE_URI, value)); _extendedToString_CACHE = null; } } public void addExtension(String tag, String prefix, String uri, String value) { synchronized (_extendedToString_LOCK) { _extensions.add(new Extension(tag, prefix, uri, value, Extension.REFERENCE_PROPERTIES)); _extendedToString_CACHE = null; } } public void addExtension(String tag, String prefix, String uri, String value, int parent) { synchronized (_extendedToString_LOCK) { _extensions.add(new Extension(tag, prefix, uri, value, parent)); _extendedToString_CACHE = null; } } public void addExtensions(final List<Extension> extensions) { synchronized (_extendedToString_LOCK) { _extensions.addAll(extensions); _extendedToString_CACHE = null; } } public List<Extension> getAllExtensions() { return Collections.unmodifiableList(_extensions); } public void clearExtensions() { synchronized (_extendedToString_LOCK) { _extensions.clear(); _extendedToString_CACHE = null; } } public String extendedToString() { synchronized (_extendedToString_LOCK) { if (_extendedToString_CACHE == null) { StringBuilder addr = new StringBuilder("PortReference < ").append("<").append(XMLUtil.WSA_PREFIX).append(":Address ").append(_address + "/>"); Iterator extensions = getExtensions(); while (extensions.hasNext()) { Extension ext = (Extension) extensions.next(); addr.append(", <").append(XMLUtil.WSA_PREFIX).append(":") .append(XMLUtil.REFERENCE_PROPERTIES_TAG).append(" ").append(ext.getPrefix()) .append(":").append(ext.getTag()).append(" : ").append(ext.getValue()).append("/>"); } _extendedToString_CACHE = addr.append(" >").toString(); } return _extendedToString_CACHE; } } private final Object _extendedToString_LOCK = new Object(); private String _extendedToString_CACHE = null;
Two things worth nothing:
1. We don't have to keep track of changes to each Extension object, as the only fields used in PortReference's extendedToString method are immutable.
2. synchronizing on a monitor object (_extendedToString_LOCK) is done rather than synchronizing the methods so we can be selective rather than locking the entire PortReference object.
3. Notice how getAllExtensions() protects changes to the _extensions list by now returning an unmodifiableList. I checked to make sure callers of this method never try to change the list.
JProfiler proved this hotspot was reduced from 10% of the total testing time to 1.4% of the total testing time once the fix was applied.
[1]
Test Details:
- Web Service requests through new HttpGateway to a single-action chain calling the XsltAction.
- 50 client threads
- each Message size is <1k
Server Environment:
- Sun JDK 1.5.0_22
- Fedora 11 (Linux 2.6.30.9-99.fc11.i686.PAE)
- 4 gigs RAM, dual-core CPU
- JBoss ESB 4.7
Client Environment:
- Sun JDK 1.6.0_17
- Fedora 11 (Linux 2.6.30.9-99.fc11.i686.PAE)
- 2 gigs RAM, dual-core CPU
- soapUI 3.0.1