This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4868-performance-improvements in repository https://gitbox.apache.org/repos/asf/tika.git
commit 92c53d17a551557cb9560bf9878ca0112d5f5818 Author: tallison <[email protected]> AuthorDate: Tue Sep 1 15:16:43 2026 -0400 TIKA-4868: improve md --- CHANGES.txt | 5 ++ .../apache/tika/sax/ToMarkdownContentHandler.java | 75 +++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 80e898aa72..397b2915b8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,10 @@ Release 4.1.0 - unreleased + * Markdown output is ~4x faster on large documents: + ToMarkdownContentHandler now buffers the commonmark renderer's + per-character writes instead of paying the synchronized + Writer.write(int) cost for every character (TIKA-4868). + * Raster previews for the vector thumbnails of Office documents: the new poi-metafile-renderer draws EMF and WMF images through POI (a PNG of a configurable width; Word's bitmap-in-WMF thumbnails from the bitmap diff --git a/tika-core/src/main/java/org/apache/tika/sax/ToMarkdownContentHandler.java b/tika-core/src/main/java/org/apache/tika/sax/ToMarkdownContentHandler.java index 33f35c9718..5903a6d818 100644 --- a/tika-core/src/main/java/org/apache/tika/sax/ToMarkdownContentHandler.java +++ b/tika-core/src/main/java/org/apache/tika/sax/ToMarkdownContentHandler.java @@ -417,11 +417,82 @@ public class ToMarkdownContentHandler extends DefaultHandler { if (finished) { return; } - renderer.render(document, writer); - writer.flush(); + // MarkdownRenderer emits mostly one char at a time; Writer.write(int) is + // synchronized and allocating in every stock Writer, which made rendering + // ~4x slower than the render logic itself on multi-MB documents. The + // unsynchronized buffer turns those calls into array stores. + RenderBuffer buffered = new RenderBuffer(writer); + renderer.render(document, buffered); + buffered.flush(); finished = true; } + /** Unsynchronized bulk buffer between the renderer and the target writer. */ + private static final class RenderBuffer extends Writer { + private final Writer out; + private final char[] buf = new char[8192]; + private int n; + + RenderBuffer(Writer out) { + this.out = out; + } + + @Override + public void write(int c) throws IOException { + if (n == buf.length) { + drain(); + } + buf[n++] = (char) c; + } + + @Override + public void write(char[] c, int off, int len) throws IOException { + if (len >= buf.length) { + drain(); + out.write(c, off, len); + return; + } + if (n + len > buf.length) { + drain(); + } + System.arraycopy(c, off, buf, n, len); + n += len; + } + + @Override + public void write(String s, int off, int len) throws IOException { + if (len >= buf.length) { + drain(); + out.write(s, off, len); + return; + } + if (n + len > buf.length) { + drain(); + } + s.getChars(off, off + len, buf, n); + n += len; + } + + private void drain() throws IOException { + if (n > 0) { + out.write(buf, 0, n); + n = 0; + } + } + + /** Flushes through to the target; the target's lifecycle stays the caller's. */ + @Override + public void flush() throws IOException { + drain(); + out.flush(); + } + + @Override + public void close() throws IOException { + flush(); + } + } + @Override public String toString() { if (finished) {
