Performance: 4x schnellere XSLT-Transformationen durch Worker-Pool
Problem: 82 XML-Dateien brauchten 160 Sekunden (JVM-Startup-Overhead) Lösung: Persistente JVM-Worker-Prozesse mit JAXP Transformer API - Saxon Worker Pool mit N persistenten JVM-Prozessen - Eliminiert JVM-Startup und Classpath-Scanning bei jedem Job - Parallele Verarbeitung mit ThreadPoolExecutor - JAXP Transformer API (javax.xml.transform) - stabil, kein System.exit() - Konfigurierbare Worker-Anzahl über Performance-Menü Ergebnis: 82 Dateien in 40 Sekunden (4x Speedup, ~0.49s pro Datei) Zusätzliche Verbesserungen: - Dual-Logging (Datei + Konsole) mit Timestamps - Worker-stderr-Logs in Projektverzeichnis/temp/ - Umfangreiche Debug-Ausgaben für Fehlerdiagnose - Robuste Fehlerbehandlung mit ErrorListener Technische Details: - SaxonWorkerPool: Verwaltet N Worker-Prozesse - JAXP statt Transform.main() (kein System.exit!) - Worker-Locks für thread-sichere Job-Verteilung - Graceful Shutdown mit EXIT-Befehl - Fallback auf subprocess bei Pool-Fehlern Dateien: - src/saxon_pool.py (NEU): Worker-Pool-Implementation - src/transform.py: Integration mit Worker-Pool - src/ui/MainWindow.py: Pool-Initialisierung, Performance-Menü - src/conf.py: max_workers Einstellung - src/main.py: Dual-Logging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+70
-19
@@ -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:
|
||||
"""
|
||||
@@ -23,6 +39,9 @@ class TransformationJob:
|
||||
Ähnlich zur TestFall-Klasse in validate-xls.py, aber für DocuMentor angepasst.
|
||||
"""
|
||||
|
||||
# Klassenweiter Cache für Saxon-Classpaths (Performance-Optimierung)
|
||||
_classpath_cache: dict[Path, str] = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
project_dir: Path,
|
||||
@@ -161,30 +180,63 @@ 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()]
|
||||
|
||||
# Sammle alle JAR-Dateien im Saxon-Verzeichnis für den Classpath
|
||||
import glob
|
||||
|
||||
# Hole Classpath aus Cache oder erstelle ihn
|
||||
saxon_dir = self.saxon_jar_path.parent
|
||||
all_jars = glob.glob(str(saxon_dir / "*.jar"))
|
||||
if saxon_dir not in TransformationJob._classpath_cache:
|
||||
# Sammle alle JAR-Dateien im Saxon-Verzeichnis für den Classpath
|
||||
import glob
|
||||
|
||||
# Sammle auch alle JARs aus dem lib-Unterordner (z.B. xmlresolver)
|
||||
lib_dir = saxon_dir / "lib"
|
||||
if lib_dir.exists() and lib_dir.is_dir():
|
||||
lib_jars = glob.glob(str(lib_dir / "*.jar"))
|
||||
all_jars.extend(lib_jars)
|
||||
logger.debug(f"Zusätzliche JARs aus lib-Verzeichnis gefunden: {len(lib_jars)}")
|
||||
all_jars = glob.glob(str(saxon_dir / "*.jar"))
|
||||
|
||||
# Verwende alle JARs im Classpath (getrennt durch : auf Linux/Mac, ; auf Windows)
|
||||
import sys
|
||||
# Sammle auch alle JARs aus dem lib-Unterordner (z.B. xmlresolver)
|
||||
lib_dir = saxon_dir / "lib"
|
||||
if lib_dir.exists() and lib_dir.is_dir():
|
||||
lib_jars = glob.glob(str(lib_dir / "*.jar"))
|
||||
all_jars.extend(lib_jars)
|
||||
logger.debug(f"Zusätzliche JARs aus lib-Verzeichnis gefunden: {len(lib_jars)}")
|
||||
|
||||
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||
classpath = classpath_separator.join(all_jars)
|
||||
# Verwende alle JARs im Classpath (getrennt durch : auf Linux/Mac, ; auf Windows)
|
||||
import sys
|
||||
|
||||
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||
classpath = classpath_separator.join(all_jars)
|
||||
|
||||
# Cache den Classpath für zukünftige Jobs
|
||||
TransformationJob._classpath_cache[saxon_dir] = classpath
|
||||
logger.debug(f"Classpath für {saxon_dir} gecacht")
|
||||
else:
|
||||
classpath = TransformationJob._classpath_cache[saxon_dir]
|
||||
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",
|
||||
@@ -196,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(
|
||||
@@ -214,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 = (
|
||||
|
||||
Reference in New Issue
Block a user