Plattformen & Cloud
Läuft überall, wo Ihr Backend läuft
Ein .NET-10-SDK ohne Druckertreiber, das Sie mit dotnet publish wie jede andere Abhängigkeit ausliefern: unter Windows, Linux und macOS, im IIS, in Docker-Containern und im Kubernetes-Cluster, on-premises oder in der Cloud wie Azure und AWS.
- Windows
- Linux
- macOS
- Docker
- Kubernetes
- Raspberry Pi
- Synology NAS
- IIS
- Azure
- AWS
Windows · Linux · macOS
Eine .NET-10-Assembly auf Basis von SkiaSharp und HarfBuzz. Die automatisierten Tests laufen auf Windows 11, macOS und Linux grün. Kein Druckertreiber und kein GUI nötig.
Container, NAS & Pi
Getestet in den .NET-Images für Debian, Ubuntu Chiseled und Azure Linux 3.0 (Distroless & NativeAOT). Selbst ein vorhandenes NAS wie eine Synology DS923+ (Container Manager) oder ein Raspberry Pi wird damit ohne Zusatzpakete zum lokalen Labelprint-Server.
Minimal-API-Beispiel
ASP.NET-Core-Beispiel mit Streaming-Endpunkten, einer begrenzten Queue (Channel + BackgroundService) und parallelen Workern.
Docker & Kubernetes: Dockerfile und Helm-Chart im Lieferumfang
- Alle Tests laufen grün in den offiziellen .NET-10-Container-Images für Debian, Ubuntu 24.04 Chiseled und Azure Linux 3.0.
Dockerfile.serverim Lieferumfang: Distroless-Image, läuft ohne Root-Rechte, mit/health-Endpunkt (oder als NativeAOT aufruntime-deps:10.0-noble-chiseled-extraab ~79 MB).- Helm-Chart für Kubernetes im Lieferumfang. Das Image bauen Sie selbst, es liegt in keiner öffentlichen Registry.
- Keine zusätzlichen Systempakete nötig, die nativen Bibliotheken bringt das NuGet-Paket mit.
- Macht aus einem vorhandenen NAS (z. B. Synology DS923+ über den Container Manager, ~90 MiB RAM, 295 PNG-Labels/s mit NativeAOT) oder einem Raspberry Pi sofort einen lokalen Labelprint-Server im Lager.
- Asynchrones ASP.NET-Core-Minimal-API-Beispiel mit Job-Queue (199.000 Labels/s ZPL-Erzeugung auf einem Mac).
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY . . RUN dotnet publish samples/WebApiDemo -c Release -o /app FROM mcr.microsoft.com/dotnet/aspnet:10.0-azurelinux3.0-distroless WORKDIR /app COPY --from=build /app . ENTRYPOINT ["dotnet","WebApiDemo.dll"]
kind: Deployment spec: replicas: 2 template: spec: containers: - name: label-api resources: requests: { cpu: 500m, memory: 512Mi } limits: { cpu: "1", memory: 1Gi } readinessProbe: httpGet: { path: /health, port: 8080 } --- kind: HorizontalPodAutoscaler spec: metrics: - type: Resource resource: name: cpu target: { type: Utilization, averageUtilization: 70 }
Performance
Schnell genug für den Versandschluss um 18 Uhr
Alle Zahlen sind gemessen, nicht geschätzt: Release-Build, .NET 10, Apple M3 Max (10 Performance- + 4 Effizienzkerne). Die Benchmarks liegen im Repository bei.
Echtes Versandetikett: über 200.000 Etiketten pro Sekunde
Gemessen am 21.10.2026 mit samples/ShippingBench: Apple M3 Max (10 Performance- + 4 Effizienzkerne, 14 insgesamt), macOS 26.6, .NET 10, Release-Build, Workstation-GC. Ende zu Ende von den Datensätzen bis zur ZPL-Datei auf der Platte, Median aus 5 Läufen nach einem Aufwärmlauf. Alle Datenquellen schreiben eine byte-identische Datei mit 131 MB (per SHA-256 geprüft), unter JIT und NativeAOT gleichermaßen.
Das Etikett
Ein Versandetikett 100 × 150 mm (4 × 6 Zoll) mit 33 Elementen: Absenderblock, invertiertes Servicefeld, Empfänger (Name, Straße, Ort, Land per Ausdruck), Routing-Code, Gewicht (0.0 kg), Packstücke, Versanddatum (Format(ToDate([ShipDate]), "dd.MM.yyyy")), QR-Code (Tracking-URL als Ausdruck), Code 128 (Sendungsnummer), GS1-128-SSCC (18 Stellen mit Prüfziffer), Seitenzähler, Rahmen und Linien. Der Druckauftrag ergänzt ein Start-Label (Tour, Depot, Gesamtgewicht und Packstücke, erste/letzte SSCC) und ein End-Label (Anzahl, erster/letzter Schlüssel).
Die Datensätze variieren wie echte Daten: Namen mit Umlauten und Doppelnamen, Firmenzusätze unterschiedlicher Länge, Türnummern, 9 Orte in AT/DE/CH/IT, 6 Services, 2,5–29 kg, 1–7 Packstücke, gültige SSCCs.
Methode
- Datensätze → ZPL → Datei auf der Platte (
StreamWriter, 64-KB-Puffer), wie bei einem Druckserver oder Batch-Job. - Byte-identische Ausgabe über alle Datenquellen, vor der Messung per SHA-256 geprüft; sonst bricht der Lauf ab.
- Gleiche Daten, gleiche Reihenfolge, gleiche Maschine; ein Aufwärmlauf, dann der Median aus 5 Läufen.
Downloads
- Etikett: PDF (Vektor, 1 Seite, 116 KB) · ZPL (203 dpi, 1,3 KB)
- Druckauftrag (Start-Label + 3 Sendungen + End-Label): PDF (5 Seiten, 615 KB) · ZPL (300 dpi, 6,3 KB)
- Bild: PNG 203 dpi · PNG 300 dpi
100.000 Versandetiketten + Start- und End-Label in eine ZPL-Datei. JIT- und NativeAOT-Build desselben Codes, Median aus 5 Läufen.
| Datenquelle | JIT Etiketten/s | NativeAOT Etiketten/s | 100.000 Etiketten dauern |
|---|---|---|---|
IReadOnlyDictionary<string, object?>-Datensätze | 249.286 | 238.590 | 0,40–0,42 s |
JSON-Datensätze (JsonElement) | 216.171 | 213.637 | 0,46–0,47 s |
POCO mit Map() je Feld | 128.165 | 91.784 | 0,78–1,09 s |
POCO, For<T>() (Reflection) | 124.995 | 83.994 | 0,80–1,19 s |
Gespeichertes Format (^DF/^XF), POCO | 109.531 | 72.113 | 0,91–1,39 s |
Für Massendaten Dictionaries oder JSON übergeben: Sie bleiben unter JIT und NativeAOT über 200.000 Etiketten/s (langsamster Einzellauf 208.068/s). C#-Objekte (POCO) erreichen unter JIT rund 125.000/s, unter NativeAOT rund 84.000/s.
5.000 Versandetiketten, gerendert mit allen 14 Kernen bei 203 dpi (ein Renderer je Worker).
| Ausgabe | JIT Etiketten/s | NativeAOT Etiketten/s |
|---|---|---|
| PNG-Vorschau | 1.224 | 1.182 |
| PCX (1 Bit) | 1.058 | 462 |
Container-Szenario: 5.000.000 Datensätze als JSON Lines (1,56 GB, ein Datensatz je Zeile), zeilenweise gelesen, als JsonElement gebunden und in eine 6,53 GB große ZPL-Datei gestreamt. NativeAOT, derselbe Apple M3 Max; Docker Desktop führt den Container in einer VM aus.
| Lauf | Etiketten/s | Zeit | Spitzenspeicher |
|---|---|---|---|
| NativeAOT unter macOS, 5 Millionen Datensätze | 132.164 | 37,8 s | 248 MB RSS |
Docker, NativeAOT linux-arm64, --memory 1g, 5 Millionen Datensätze | 91.133 | 54,9 s | 90 MB |
| Docker, 100.000 Etiketten, Dictionary-Datensätze | 157.489 | 0,64 s | |
| Docker, 100.000 Etiketten, JSON-Datensätze | 119.271 | 0,84 s | |
| Docker, PNG-Vorschau, 14 Kerne | 1.164 |
Der Speicher bleibt bei jeder Anzahl an Datensätzen konstant: zplCore zählt oder puffert eine gestreamte Quelle nicht. Image: 127 MB (runtime-deps:10.0-noble-chiseled-extra, nur das native Programm, ohne Root). Datei-I/O auf das Docker-Volume ist langsamer als nativ, daher die niedrigeren Werte.
Selbst nachmessen
Der Benchmark liegt im Repository bei. sample schreibt das Etikett als PNG, PDF und ZPL; das Docker-Image entsteht aus build/Dockerfile.shippingbench. Die Werte hängen von Ihrer Hardware ab; sie sind Messungen, keine Garantie.
# JIT dotnet run -c Release --project samples/ShippingBench # NativeAOT (derselbe Code) dotnet publish samples/ShippingBench -c Release -r osx-arm64 -p:PublishAot=true -o out # Docker (NativeAOT) + 5 Millionen JSON-Lines-Datensätze docker build -f build/Dockerfile.shippingbench -t zplcore-shippingbench . docker run --rm -v zplcore-bench:/data zplcore-shippingbench 1000 10 1 5000000 # Etikett als PNG/PDF/ZPL dotnet run -c Release --project samples/ShippingBench -- sample out/sample
Weitere Messungen: kleines Label mit 7 Elementen
Die folgenden Zahlen stammen aus samples/PerfTest, samples/RenderBench und samples/WebApiDemo, überwiegend mit einem kleinen Label 100 × 60 mm (PerfTest: 7 Elemente, also Text, Ausdruck, Zahlenformat, Code 128, Zähler), nicht mit dem Versandetikett oben. Derselbe Apple M3 Max.
Ein Druckauftrag: Start-Label + 100.000 Sendungs-Labels + End-Label, Schnitt nur nach dem letzten Label. 14 Kerne, samples/PerfTest.
| Szenario | Zeit | Labels/s | Größe | Speicher (Heap) |
|---|---|---|---|---|
Objekte → ZPL, gespeichertes Format (^DF/^XF) | 0,73 s | 136.000 | 14,7 MB | 24 MB |
| Objekte → ZPL, volles Format je Label | 0,39 s | 256.000 | 29,1 MB | 24 MB |
EF Core / SQLite AsAsyncEnumerable() → Datei | 0,57 s | 176.000 | 14,7 MB | 29 MB |
| LiteDB-Query → Datei | 0,50 s | 202.000 | 14,7 MB | 64 MB |
| PNG-Vorschau sequenziell (799 × 480 px, Graustufen) | 3,4 min | 490 | 18,2 MB / 2.000 | 84 MB |
| PNG-Vorschau parallel (14 Kerne) | 24,8 s | 4.033 | 17,1 MB / 2.000 | 106 MB |
Die ZPL-Erzeugung ist praktisch kostenlos und streamt mit konstantem Speicher. Das gespeicherte Format halbiert die Datenmenge zum Drucker (14,7 statt 29,1 MB).
ASP.NET-Core-Minimal-API aus samples/WebApiDemo: 14 Kerne, Server-GC, 14 Worker, Label 100 × 60 mm bei 203 dpi. Client und Server im selben Prozess.
| Szenario | Labels/s | MB/s | p50 | p95 | GC-Heap |
|---|---|---|---|---|---|
| JSON-Payload → ZPL | |||||
| 1.000 Datensätze/Request | 62.857 | 20,3 | 16,2 ms | 16,7 ms | 178 MB |
| 10.000 Datensätze/Request | 112.484 | 36,4 | 76,1 ms | 136,9 ms | 183 MB |
| 100.000 Datensätze/Request | 186.572 | 60,5 | 527,5 ms | 565,2 ms | 414 MB |
| DB-Streaming → ZPL | |||||
| 10.000 Datensätze | 151.744 | 49,1 | 69,6 ms | 73,1 ms | 361 MB |
| 100.000 Datensätze | 199.102 | 64,6 | 504,3 ms | 505,2 ms | 372 MB |
| Gleichzeitige Clients | |||||
| JSON 1k, 1 Client | 196.112 | 63,4 | 4,8 ms | 8,6 ms | 404 MB |
| JSON 1k, 4 Clients | 240.453 | 77,7 | 16,4 ms | 21,7 ms | 404 MB |
| JSON 1k, 16 Clients | 155.344 | 50,2 | 100,8 ms | 120,8 ms | 419 MB |
| DB 10k, 4 Clients | 215.699 | 69,7 | 186,2 ms | 202,4 ms | 395 MB |
| DB 10k, 16 Clients | 179.860 | 58,2 | 878,0 ms | 907,6 ms | 408 MB |
| Vorschau (50 Labels/Request) | |||||
| PDF, 1 Client | 583 | 5,0 | 87,0 ms | 94,0 ms | 267 MB |
| PNG (zip), 1 Client | 484 | 4,6 | 108,3 ms | 108,7 ms | 269 MB |
| PDF, 14 Clients | 5.085 | 43,5 | 131,8 ms | 154,4 ms | 472 MB |
| PNG (zip), 14 Clients | 3.995 | 38,1 | 162,0 ms | 188,1 ms | 473 MB |
| Job-Queue (alle Jobs gleichzeitig eingereicht, Latenz inkl. Wartezeit) | |||||
| 256 × JSON 1k → ZPL (153 Jobs/s) | 153.438 | 49,6 | 98,8 ms | 365,5 ms | 885 MB |
| 64 × DB 10k → ZPL (18,6 Jobs/s) | 186.361 | 60,3 | 2.236,5 ms | 3.411,1 ms | 828 MB |
| 64 × JSON 50 → PDF (104 Jobs/s) | 5.396 | 46,2 | 345,2 ms | 445,6 ms | 467 MB |
Die ZPL-Erzeugung sättigt bei 199.000 Labels/s (ab 4 parallelen Requests), weitere Clients erhöhen nur die Latenz. Rendern kostet 40× mehr und skaliert mit den Kernen.
Rendern + PNG
Labels/s · Faktor ggü. 1 ThreadVorher (heller Balken): 499 → 1.826/s, maximal 4,5× bei 8 Threads.
Nur Text
Labels/s · Faktor ggü. 1 ThreadVorher wurde Text mit mehr Threads langsamer (0,6× bei 8 Threads), weil Skias Glyph-Cache global sperrt.
Gemessen mit RenderBench -- 150 scale auf dem M3 Max. Glyphen landen einmal pro Schrift und Größe in einem sperrfreien Cache, so entfallen die globalen Sperren von SkiaSharp.
Pro Label 100 × 60 mm bei 203 dpi. Graustufen- bzw. 1-Bit-PNG statt RGBA, direkt gezeichnete ^GB-Rahmen, sperrfreier Glyph-Cache.
| Messung | Vorher | Jetzt | Verbesserung |
|---|---|---|---|
| Rendern zu Bitmap | 1,28 ms | 0,39 ms | −70 % |
| Rendern + PNG | 8,19 ms | 1,86 ms | −77 % |
| PNG-Größe | 18 KB | 9 KB | −50 % |
| 100.000 Vorschauen sequenziell | 14,5 min | 3,4 min | −77 % |
| 100.000 Vorschauen parallel | 2,2 min | 25 s | −81 % |
| Rendern + PNG, 10 Threads | 1.826/s | 5.022/s | 2,8× |
| Nur Text, 8 Threads | 3.162/s | 42.434/s | 13,4× |
| Arbeitslast | Labels | Speicher |
|---|---|---|
| Dauertest, kleine Labels, parallel | 1.000.000 | RSS 128–138 MB · GC-Heap 2–3 MB |
| Dauertest, 4 × 6″-PNGs, 14 Threads | 1.000.000 | RSS 438–481 MB |
| ZPL-Streaming aus Objekten | 100.000 | Heap 24 MB |
| Streaming aus EF Core / SQLite in eine ZPL-Datei | 100.000 | Heap 29 MB |
| Streaming aus LiteDB in eine ZPL-Datei | 100.000 | Heap 64 MB |
Methodik & Edge/NAS-Container
Release-Build, .NET 10, Apple M3 Max (10 Performance- + 4 Effizienzkerne). Zusätzlich als NativeAOT-Container (runtime-deps:10.0-noble-chiseled-extra, ~79 MB) auf einem Synology DS923+ NAS (AMD Ryzen R1600, 2 Kerne / 4 Threads) gemessen: 237.428 Labels/s Parsen, 1.233 Labels/s Bitmap-Rendern und 295 PNG-Labels/s (> 17.500 PNGs/min). Alle Messungen lassen sich mit einem Befehl wiederholen.
# 100.000-Label-Druckauftrag dotnet run -c Release --project samples/PerfTest -- 100000 # Thread-Skalierung (label|text|barcode|box|empty) dotnet run -c Release --project samples/RenderBench -- 150 scale

