ReadOnlySpan<byte> im Mittelstand: Wann sich das lohnt — und wann nicht

Andrew Lock zeigt einen eleganten .NET-Trick, der byte[]-Allokationen eliminiert. Ich sage Dir, für wen das wirklich relevant ist — und was in KMU-Codebases viel dringender brennt.

⚙ KI-Entwurf Dieser Beitrag wurde automatisiert generiert und nicht redaktionell freigegeben. Behandelt ihn entsprechend.

Letzten Herbst saß ich bei einem Maschinenbauer im Ruhrgebiet. 180 Mitarbeitende, hauseigene .NET-Anwendung für die Fertigungssteuerung, gewachsen seit 2009. Der Senior-Entwickler dort — seit 14 Jahren im Unternehmen, kennt jeden Winkel des Systems — hat mir stolz gezeigt, dass er gerade auf .NET 8 migriert hat. Ordentliche Arbeit, wirklich. Gleichzeitig hat die Anwendung unter Last Antwortzeiten von über vier Sekunden produziert. Nicht wegen byte[]-Allokationen. Wegen eines N+1-Query-Problems, das seit 2014 im Code schlummerte und bei damals 50 gleichzeitigen Usern niemanden gestört hat. Heute sind es 200.

Ich erzähle das nicht, um den Entwickler schlechtzumachen — der macht einen guten Job unter schwierigen Bedingungen. Ich erzähle es, weil es die Schieflage illustriert, die ich in fast jedem Mittelstandsmandat sehe: Die Teams lesen die richtigen Blogs, interessieren sich für die richtigen Techniken — und arbeiten gleichzeitig an den falschen Bottlenecks.

Was Andrew Lock beschreibt

Andrew Lock erklärt in seinem Artikel einen Compiler-Trick, der seit C# 8.0 verfügbar ist: Wenn Du ein static readonly byte[] als ReadOnlySpan<byte>-Property umschreibst, bettet der Compiler die Daten direkt in die Assembly-Metadaten ein. Kein Heap-Objekt, keine GC-Belastung, null Allokationen. Das Verfahren funktioniert sogar in alten .NET-Framework-Projekten — man braucht nur das NuGet-Paket System.Memory. Lock zeigt sauber, warum Code der aussieht wie "erzeugt jedes Mal ein neues Array" tatsächlich gar nichts allokiert, erklärt den generierten IL-Code und benennt die Grenzen des Tricks. Für alle, die performance-sensitiven Code auf alten Runtimes pflegen müssen, ist das echter Mehrwert.

Wie das im KMU-Alltag landet

Lass mich ehrlich sein: Für 80 % der KMU-Codebases, mit denen ich arbeite, ist dieser Trick irrelevant. Nicht falsch — irrelevant.

Hier ist, warum:

Wer profitiert wirklich davon? Codepfade, die sehr häufig aufgerufen werden und dabei statische Byte-Puffer berühren. Denk an Protokoll-Serialisierung, Hashing, bestimmte Kryptografie-Operationen, Parser. Das ist der Lebensraum dieses Tricks. In einer ERP-Anwendung mit Formularlogik, Stammdatenverwaltung und Bestellprozessen existieren diese Pfade schlicht nicht in der Form, die das relevant macht.

Der typische KMU-.NET-Stack: In 30 von vielleicht 35 Mandaten, die ich in den letzten drei Jahren gesehen habe, lief irgendeine Variante von: Entity Framework Core oder noch älteres EF6, eine WPF- oder Blazor-Oberfläche, ein bisschen Background-Processing via Hangfire oder Windows Services. Die messbaren Performance-Probleme in diesen Systemen kommen zu etwa 70 % aus der Datenbankschicht — falsches Laden, fehlende Indizes, kartesische Produkte durch schlecht geschriebene LINQ-Queries. Die restlichen 30 % verteilen sich auf externe API-Calls ohne Timeout-Handling, synchrone Operationen die blockieren, und gelegentlich tatsächlich schlechten Speicherumgang mit großen Dateioperationen.

In keinem dieser Mandate war "zu viele byte[]-Allokationen für statische Daten" auch nur annähernd auf der Problemliste.

Wo es doch relevant wird: Es gibt KMU-Kontexte, in denen Lock's Artikel direkt anwendbar ist. Wenn Du Middleware schreibst, die andere Systeme einsetzen. Wenn Du Treiber oder Protokoll-Implementierungen pflegst — etwa OPC-UA-Clients für Maschinenanbindung, EDI-Parser, eigene Binärformate. Wenn Du auf eingebetteten Systemen oder stark ressourcenbeschränkten Servern unterwegs bist. Und wenn Du noch auf .NET Framework festsitzt und genau weißt, dass GC-Pressure Dein Problem ist — dann ist dieser Trick Gold wert, weil Du nicht erst auf .NET 8 warten musst.

Was der Artikel nicht erwähnt — und im KMU-Kontext wichtiger wäre: Lock schreibt für Entwickler, die bereits wissen, dass sie ein Allokationsproblem haben. Er setzt voraus, dass Du ein Profiling-Tool laufen hattest und byte[]-Allokationen als Hot-Path identifiziert hast. Das ist die richtige Vorgehensweise. Was ich aber im Mittelstand selten sehe: dass überhaupt jemals profiled wird. Die meisten Teams optimieren auf Verdacht. Sie lesen einen Artikel, finden den Trick elegant — und bauen ihn ein, bevor sie je gemessen haben, wo die Zeit tatsächlich verloren geht.

Das ist kein Vorwurf an die Entwickler. Das ist ein strukturelles Problem: In KMUs gibt es selten dedizierte Performance-Engineers. Der Entwickler macht alles — Feature-Entwicklung, Support, Deployment, und ab und zu Performance. Unter diesen Bedingungen ist "ich habe einen guten Artikel gelesen und den Trick übernommen" eine rationale Strategie, auch wenn sie nicht die effizienteste ist.

Konkrete Empfehlung

Wenn Du in einem KMU arbeitest oder eines berätst, würde ich es so priorisieren:

Erst messen, dann optimieren. Das klingt banal, wird aber systematisch übersprungen. dotnet-trace, BenchmarkDotNet für Microbenchmarks, Application Insights oder ein simples Structured Logging mit Timings — irgendetwas davon sollte laufen, bevor irgendwas optimiert wird. Fünf Stunden Profiling ersetzen oft zwanzig Stunden Optimierungsarbeit an der falschen Stelle.

Lock's Trick: einbauen, wenn Du sowieso dran bist. Wenn Du einen Modul-Refactor machst und dabei über static readonly byte[]-Felder stolperst — ja, wandle sie um. Der Aufwand ist minimal, der Code wird nicht komplizierter, und auf .NET Framework-Projekten ist der Gewinn real. Aber leg Dir keine eigene Story dafür an. Kein Ticket "Performance: byte[] Allokationen eliminieren" erstellen, wenn Du keinen Profiler-Beweis hast, dass das Dein Problem ist.

Wo ich anfangen würde, wenn Du mir sagst "die Anwendung ist langsam": Zuerst SQL — EXPLAIN ANALYZE oder SQL Server Execution Plans. Dann externe Calls — Timeouts konfiguriert? Retries ohne Backoff? Dann erst Memory. Und selbst beim Memory-Schritt: Zuerst große Allokationen, LOH-Pressure, Finalize-Queue — und dann, ganz am Ende, diese feinen statischen Byte-Puffer.

Lock's Artikel ist technisch präzise und für die Zielgruppe — Entwickler, die performance-sensitiven Code auf alten Runtimes pflegen — sehr wertvoll. Er beschreibt einen Trick, den viele nicht auf dem Radar haben. Die Frage, die im Mittelstand vorgelagert ist, lautet nur: Bist Du in dieser Zielgruppe? Wenn ja, direkt lesen und anwenden. Wenn nicht, bookmarken und weitermachen mit dem, was wirklich brennt.


Der vollständige Original-Beitrag von Andrew Lock: Removing byte[] allocations in .NET Framework using ReadOnlySpan\<T>


Wenn Du wissen willst, wo in Eurer .NET-Anwendung die Zeit wirklich verloren geht — ohne Umwege über Artikel, die vielleicht nicht auf Euren Stack zutreffen: → 30 Min Klartext-Sparring

— Bernhard