Performance-Revolution: Saxon-Worker-Pool eliminiert JVM-Startup-Overhead

Implementiert persistente JVM-Worker-Pool für 5-10x schnellere Transformationen:

VORHER:
- 82 Dateien in 60s (12 Worker) = 0.73s/Datei
- JVM-Start bei jeder Transformation (~500ms Overhead)
- Classpath wird jedes Mal neu geladen

NACHHER (erwartet):
- 82 Dateien in ~8-12s (12 Worker) = 0.10-0.15s/Datei
- JVM läuft persistent (einmalig ~500ms beim Start)
- 5-10x schneller! 🚀

Architektur:
- SaxonWorkerPool: Verwaltet N lang-laufende JVM-Prozesse
- SaxonWorker.java: Java-Daemon der Saxon-Transformationen ausführt
- Kommunikation via stdin/stdout (Tab-separated Job-Format)
- Automatisches Fallback auf subprocess bei Pool-Fehlern
- Graceful Shutdown beim Beenden der Anwendung

Neue Dateien:
- src/saxon_pool.py: Worker-Pool-Implementierung
  - Kompiliert SaxonWorker.java zur Laufzeit
  - Startet N JVM-Prozesse beim Projekt-Öffnen
  - Thread-safe Job-Verteilung mit Locks
  - Context Manager für sauberen Shutdown

Änderungen:
- transform.py: Nutzt Pool wenn verfügbar, Fallback auf subprocess
- MainWindow.py: Initialisiert Pool beim Projekt-Öffnen, beendet bei Close
- set_saxon_worker_pool() zum globalen Pool-Management

Technische Details:
- Java-Code als String eingebettet, Runtime-Kompilierung mit javac
- stdout für Job-Ergebnisse, stderr für Saxon-Logs
- Tab-separated Format: source\txsl\toutput\tparams
- Worker antworten mit "OK" oder "ERROR: message"

Nächster Test wird zeigen ob 8-12s erreicht werden! 🎯

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 13:40:00 +01:00
parent 5ecad6ce89
commit b30bb0ed2d
3 changed files with 438 additions and 6 deletions
+44 -5
View File
@@ -11,10 +11,26 @@ import logging
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Any
from typing import Any, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from saxon_pool import SaxonWorkerPool
logger = logging.getLogger(__name__)
# Globaler Saxon-Worker-Pool (wird von MainWindow initialisiert)
_saxon_worker_pool: Optional["SaxonWorkerPool"] = None
def set_saxon_worker_pool(pool: Optional["SaxonWorkerPool"]):
"""Setzt den globalen Saxon-Worker-Pool."""
global _saxon_worker_pool
_saxon_worker_pool = pool
if pool:
logger.info(f"Saxon-Worker-Pool aktiviert mit {pool.num_workers} Workern")
else:
logger.info("Saxon-Worker-Pool deaktiviert (Fallback auf subprocess)")
class TransformationJob:
"""
@@ -164,6 +180,31 @@ class TransformationJob:
logger.error(error_msg)
return False, error_msg
logger.info(f"Starte Saxon-Transformation: {self.xml_file.name}")
# Versuche zuerst den Worker-Pool zu nutzen (schneller!)
global _saxon_worker_pool
if _saxon_worker_pool:
try:
success, message = _saxon_worker_pool.transform(
source_xml=xml_abs,
xsl_stylesheet=self.xsl_file,
output_fo=self.temp_fo,
xslt_params=self.xslt_params,
)
if success:
logger.info(f"Saxon-Transformation erfolgreich (Worker-Pool): {self.xml_file.name}")
else:
logger.error(f"Saxon-Transformation fehlgeschlagen (Worker-Pool): {message}")
return success, message
except Exception as e:
logger.warning(f"Worker-Pool-Fehler, Fallback auf subprocess: {e}")
# Fallback auf subprocess unten
# Fallback: Traditionelle subprocess-Methode (langsamer, aber robuster)
# XSLT-Parameter formatieren
params = [f"{key}={value}" for key, value in self.xslt_params.items()]
@@ -196,7 +237,6 @@ class TransformationJob:
logger.debug("Classpath aus Cache verwendet")
# Saxon-Kommandozeile
# Verwende -cp mit allen JARs und rufe Transform-Main direkt auf
cmd_line = [
str(self.java_vm_path),
"-cp",
@@ -208,8 +248,7 @@ class TransformationJob:
*params,
]
logger.info(f"Starte Saxon-Transformation: {self.xml_file.name}")
logger.debug(f"Kommandozeile: {' '.join(cmd_line)}")
logger.debug(f"Kommandozeile (subprocess fallback): {' '.join(cmd_line)}")
try:
result = subprocess.run(
@@ -226,7 +265,7 @@ class TransformationJob:
logger.debug(f"Saxon StdErr:\n{result.stderr}")
if result.returncode == 0:
logger.info(f"Saxon-Transformation erfolgreich: {self.xml_file.name}")
logger.info(f"Saxon-Transformation erfolgreich (subprocess): {self.xml_file.name}")
return True, "Erfolgreich"
else:
error_msg = (