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) {

Reply via email to