xom-interest AT lists.ibiblio.org
Subject: XOM API for Processing XML with Java
List archive
- From: Wolfgang Hoschek <whoschek AT lbl.gov>
- To: xom-interest AT lists.ibiblio.org
- Subject: [XOM-interest] Serializer performance patches
- Date: Tue, 28 Jun 2005 21:55:54 -0700
Here are some patches that substantially improve Serializer performance for Unicode encoding, with and without indenting, with essentially zero overhead for non-Unicode encodings.
With this my serialization measurements went from some 5MB/s to some 18 MB/s with UTF-8, indent=0 and 4 (JDK 1.5 server VM, Linux):
[pabst /home/portnoy/u5/hoschek/tmp/tmp/firefish] bin/fire-java nux.xom.tests.BinaryXMLTest ser xom 0 6000 data/samples/data/ periodic.xml
patchesEnabled=true
now processing data/samples/data/periodic.xml
compression factor = 1.0
****** SUMMARY ******
files = 6000
secs = 35.496
mean throughput MB/s = 18.240555
mean compression factor = 1.0
files/sec = 169.03314
checksum = 678918000
Dumping CPU usage by sampling running threads ... done.
Most importantly, it's taking care of TextWriter.{writeMarkup, writePCData and writeAttributeValue} with a fast path for Unicode, and to a lesser but still significant extent the JDK 1.5 char[] allocation & copying issue in TextWriter.{incrementIndent, decrementIndent}. It also eliminates the trailing flush() of invoked by Serializer.write(Element).
The latter elimination of flush() in Serializer.write(Element) might be problematic for folks that have existing exotic subclasses of Serializer. It's not a problem for normal users since write(Document) calls flush() anyay. Still, I'm not sure that piece of the patch should be applied. The question is: Why would anyone like to flush the stream after *each* element in the tree?
The other parts have no publicly visible effect, except for improved performance of course.
As far as I can see, the performance profile now has no obvious hotspots anymore, except perhaps the costly iteration over the additional namespace declarations (even if there are none present at all). Perhaps a data structure that is both more space and time efficient could be found for the additional namespace declarations, maybe worth a thought.
Anyway here's the (somewhat unpolished) patch for Serializer and TextWriter:
TextWriter.java:
private final boolean isUnicodeWriter = this instanceof UnicodeWriter; // WH
final void writePCDATA(String s) throws IOException {
if (normalize) {
s = Normalizer.normalize(s, Normalizer.NFC);
}
if (isUnicodeWriter && !mightNeedPCDATAEscaping(s)) { // WH
out.write(s);
return;
}
int length = s.length();
for (int i=0; i < length; i++) {
writePCDATA(s.charAt(i));
}
}
final private static boolean mightNeedPCDATAEscaping(String s) { // WH
int length = s.length();
for (int i = 0; i < length; i++) {
switch (s.charAt(i)) {
case '&': return true;
case '<': return true;
case '>': return true;
case '\r': return true;
}
}
return false;
}
final void writeAttributeValue(String s)
throws IOException {
if (normalize) {
s = Normalizer.normalize(s, Normalizer.NFC);
}
if (isUnicodeWriter && !mightNeedAttributeEscaping(s)) { // WH
out.write(s);
return;
}
int length = s.length();
for (int i=0; i < length; i++) {
writeAttributeValue(s.charAt(i));
}
}
private static final boolean mightNeedAttributeEscaping(String s) { // WH
int length = s.length();
for (int i = 0; i < length; i++) {
switch (s.charAt(i)) {
case '\t': return true;
case '\n': return true;
case '"': return true;
case '\r': return true;
case '&': return true;
case '<': return true;
case '>': return true;
}
}
return false;
}
final void writeMarkup(String s) throws IOException {
if (normalize) {
s = Normalizer.normalize(s, Normalizer.NFC);
}
if (isUnicodeWriter) { // WH
out.write(s);
return;
}
int length = s.length();
for (int i=0; i < length; i++) {
writeMarkup(s.charAt(i));
}
}
boolean isIndenting() {
return indentString.length() > 0;
}
private int fakeIndents = 0;
private static final String INDENTS; // WH
static {
int len = 128; // enough room for 128/4 = 32 typical nesting levels
StringBuffer buf = new StringBuffer(len);
for (int i=0; i < len; i++) buf.append(' ');
INDENTS = buf.toString();
}
void incrementIndent() {
if (indent == 0) return; // WH
String newIndent;
if (indentString.length() + indent < INDENTS.length()) { // fast path
newIndent = INDENTS.substring(0, indentString.length() + indent); // mem overlay
} else {
// slow path
// expensive in JDK 1.5 (which no more shares the backing array of String and StringBuffer
StringBuffer buf = new StringBuffer(indentString);
for (int i = 0; i < indent; i++) {
buf.append(' ');
}
newIndent = buf.toString();
}
// limit maximum indent to half of maximum line length
if (maxLength > 0 && newIndent.length() > maxLength / 2) {
fakeIndents++;
}
// else this.indentString = newIndent.toString();
else this.indentString = newIndent;
}
void decrementIndent() {
if (indent == 0) return; // WH
if (fakeIndents > 0) fakeIndents--;
else {
indentString = indentString.substring(
0, indentString.length()-indent
);
}
}
Serializer.write(Element) :
{ ...
// escaper.flush(); // WH not needed?
}
-
[XOM-interest] Serializer performance patches,
Wolfgang Hoschek, 06/29/2005
- Re: [XOM-interest] Serializer performance patches, Elliotte Harold, 06/29/2005
-
Re: [XOM-interest] Serializer performance patches,
Elliotte Harold, 06/29/2005
- Re: [XOM-interest] Serializer performance patches, Wolfgang Hoschek, 06/29/2005
-
Re: [XOM-interest] Serializer performance patches,
Elliotte Harold, 06/29/2005
- Re: [XOM-interest] Serializer performance patches, Wolfgang Hoschek, 06/29/2005
- Re: [XOM-interest] Serializer performance patches, Elliotte Harold, 06/29/2005
-
Re: [XOM-interest] Serializer performance patches,
Elliotte Harold, 06/29/2005
- Re: [XOM-interest] Serializer performance patches, Wolfgang Hoschek, 06/29/2005
- Re: [XOM-interest] Serializer performance patches, Wolfgang Hoschek, 06/29/2005
- Re: [XOM-interest] Serializer performance patches, Elliotte Harold, 06/29/2005
Archive powered by MHonArc 2.6.24.