Skip to Content.
Sympa Menu

xom-interest - [XOM-interest] Serializer performance patches

xom-interest AT lists.ibiblio.org

Subject: XOM API for Processing XML with Java

List archive

Chronological Thread  
  • 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?
}





Archive powered by MHonArc 2.6.24.

Top of Page