پشتیبانی از فشردهسازی Zstandard (Zstd) در NET 11.
نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۳/۱۳ ۰۸:۲۰
آدرس: www.dntips.ir
DeflateStream، GZipStream، ZLibStream و در نسخههای اخیر BrotliStream، ابزارهای قدرتمندی را در اختیار توسعهدهندگان قرار داده است. با این حال، نیاز به الگوریتمی که بتواند تعادلی بینقص میان سرعت فوقالعاده بالا و نسبت فشردهسازی ایدهآل برقرار کند، همواره در پروژههای مقیاسبزرگ احساس میشد.ZstandardStream به این مجموعه اضافه شده است که به ما اجازه میدهد از پتانسیل عظیم الگوریتم Zstandard (یا به اختصار Zstd) به صورت بومی (Native) بدون نیاز به کتابخانههای شخص ثالث استفاده کنیم. در این مقاله، به بررسی ساختار این الگوریتم، قابلیتها، نحوه پیادهسازی و ابزارهای جدید آن در داتنت ۱۱ خواهیم پرداخت.7- (سریعترین حالت با کمترین نرخ فشردهسازی) تا 22 (کندترین حالت اما با بیشترین میزان فشردهسازی) پشتیبانی میکند. حالت پیشفرض این الگوریتم روی سطح 3 تنظیم شده است که توازن بهینهای برای اکثر سناریوهای عمومی فراهم میسازد.جایگاه صنعتی Zstandard: کارایی بالای Zstd باعث شده است که در پروژههای بزرگ جهانی به عنوان استاندارد فشردهسازی انتخاب شود؛ از جمله سیستمفایلهای لینوکس (btrfs و SquashFS)، مدیریت بستههای توزیعهای آرچلینوکس و فدورا، پلتفرم ابری AWS Redshift، سیستم Core Dump در FreeBSD و حتی آرشیو بازیهای کنسول نینتندو سوییچ.
Brotli* کار کرده باشید، استفاده از ابزارهای جدید برای شما بسیار آشنا و ساده خواهد بود. فضای نام System.IO.Compression میزبان چهار تایپ (Type) اصلی جدید است:ZstandardStream: یک کلاس بستهبندی استریم (Stream Wrapper) جهت مدیریت جریانهای دادهای.ZstandardEncoder / ZstandardDecoder: ساختارهایی مبتنی بر Struct، بهینه برای مدیریت حافظه و سازگار با Span.ZstandardDictionary: قابلیتی منحصربهفرد جهت فشردهسازی دادههای کوچک و مکرر با بازدهی بالا.ZstandardCompressionOptions: کلاسی برای تنظیم دقیق کیفیت، اندازه پنجره (Window Size) و ویژگیهای اعتبارسنجی.// Compress (فشردهسازی)
using var output = new MemoryStream();
using (var zstd = new ZstandardStream(output, CompressionMode.Compress))
{
await inputStream.CopyToAsync(zstd);
}
// Decompress (باز کردن فشردهسازی)
using var zstdDecompress = new ZstandardStream(compressedStream, CompressionMode.Decompress);
await zstdDecompress.CopyToAsync(outputStream);ZstandardEncoder و ZstandardDecoder به دلیل عدم تخصیص حافظه اضافی در بخش Heap و هماهنگی با Span، بازدهی به مراتب بالاتری دارد:byte[] source = File.ReadAllBytes("data.bin");
// Compress
int maxLength = ZstandardEncoder.GetMaxCompressedLength(source.Length);
var compressed = new byte[maxLength];
ZstandardEncoder.TryCompress(source, compressed, out int bytesWritten);
// Decompress
int decompressedSize = ZstandardDecoder.GetMaxDecompressedLength(compressed.AsSpan(0, bytesWritten));
var decompressed = new byte[decompressedSize];
ZstandardDecoder.TryDecompress(compressed.AsSpan(0, bytesWritten), decompressed, out int bytesRead);ZstandardCompressionOptions میتوان کیفیت فشردهسازی را به صورت عددی سفارشیسازی کرد. در داتنت، گزینه عمومی CompressionLevel.Fastest به کمترین مقدار عددی و CompressionLevel.SmallestSize به سقف آن یعنی ۲۲ نگاشت میشود:var options = new ZstandardCompressionOptions
{
Quality = 6 // بازه عددی بین 1 تا 22، مقدار پیشفرض 3 است
};
using var zstd = new ZstandardStream(output, options);Program.cs، داتنت به طور خودکار هدر درخواستهای حاوی Accept-Encoding: zstd را شناسایی کرده و پاسخ را فشرده میکند:// Program.cs
builder.Services.AddResponseCompression(x =>
{
x.EnableForHttps = true;
x.Providers.Add<ZstandardCompressionProvider>();
});IDecompressionProvider به همین سادگی مکانیزم معکوس را پیادهسازی و مدیریت میکند.// آموزش دیکشنری از روی نمونهها byte[] allSamples = ...; // الحاق تمام دادههای نمونه به هم int[] lengths = ...; // طول تکتک نمونهها using ZstandardDictionary dict = ZstandardDictionary.TrainFromSamples(allSamples, lengths, maxDictionarySize: 100_000); // فشردهسازی با کمک دیکشنری - هر دو سمت باید از یک دیکشنری یکسان استفاده کنند using var encoder = new ZstandardEncoder(dict); encoder.Compress(payload, compressed, out int consumed, out int written, isFinalBlock: true);
نکته: طبق توصیههای استاندارد، حجم بهینه برای دیکشنری حداکثر ۱۰۰ کیلوبایت است و برای آموزش بهینه آن بهتر است حجم دادههای نمونه حدود ۱۰۰ برابر حجم نهایی دیکشنری (حدود ۱۰ مگابایت داده نمونه واقعی و متناوب) باشد تا بالاترین نرخ بازدهی حاصل شود.
HttpClient و در نهایت پیکربندی پیشرفته و انتخابی آن را در سرور وب Kestrel بررسی میکنیم.Brotli ،GZip و Deflate)، میتوانیم یک فایل متنی بزرگ (مانند رمان معروف «جنگ و صلح» اثر لئو تولستوی) را به عنوان ورودی در نظر بگیریم. ابتدا فایل متنی را در تنظیمات پروژه (.csproj) کپی میکنیم:<ItemGroup>
<None Include="war-and-peace.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>using System.IO.Compression;
using System.Reflection;
const string fileName = "war-and-peace.txt";
var targetPath = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory!.FullName;
// 1. فشردهسازی با ZStandard
await using (var inputStream = File.OpenRead(fileName))
await using (var outputStream = File.Create(Path.Combine(targetPath, "ZStandard.compressed")))
await using (var compressStream = new ZstandardStream(outputStream, CompressionMode.Compress))
{
await inputStream.CopyToAsync(compressStream);
}
// 2. فشردهسازی با Brotli
await using (var inputStream = File.OpenRead(fileName))
await using (var outputStream = File.Create(Path.Combine(targetPath, "Brotli.compressed")))
await using (var compressStream = new BrotliStream(outputStream, CompressionMode.Compress))
{
await inputStream.CopyToAsync(compressStream);
}
// 3. فشردهسازی با Gzip
await using (var inputStream = File.OpenRead(fileName))
await using (var outputStream = File.Create(Path.Combine(targetPath, "Gzip.compressed")))
await using (var compressStream = new GZipStream(outputStream, CompressionMode.Compress))
{
await inputStream.CopyToAsync(compressStream);
}
// 4. فشردهسازی با Deflate
await using (var inputStream = File.OpenRead(fileName))
await using (var outputStream = File.Create(Path.Combine(targetPath, "Deflate.compressed")))
await using (var compressStream = new DeflateStream(outputStream, CompressionMode.Compress))
{
await inputStream.CopyToAsync(compressStream);
}Quality = 3) ممکن است حجم خروجی بزرگتری نسبت به Brotli یا GZip ارائه دهد، اما برگ برنده آن سرعت بسیار بالاتر در عملیات فشردهسازی و بخصوص باز کردن فشردهسازی (Decompression) همراه با مصرف بهینه CPU است. برای دستیابی به حجم کمتر در Zstd، میتوان به سادگی پارامتر Quality را روی مقادیر بالاتر (تا ۲۲) تنظیم کرد.AutomaticDecompression در HttpClientHandler به راحتی دادههای فشردهسازی شده با Zstd را به صورت خودکار از حالت فشرده خارج کنید.var handler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Zstandard
};
var client = new HttpClient(handler);
// سرور فیسبوک به طور پیشفرض از Zstandard پشتیبانی میکند
var response = await client.GetStringAsync("https://facebook.com");
Console.WriteLine($"Length of decompressed content: {response.Length}");var handler = new HttpClientHandler
{
// استفاده از عملگر بیتی OR برای تعریف چند الگوریتم به طور همزمان
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Zstandard
};var builder = WebApplication.CreateBuilder(args);
// ثبت سرویسهای فشردهسازی پاسخ (به صورت خودکار Zstandard فعال میشود)
builder.Services.AddResponseCompression();
var app = builder.Build();
// فعالسازی میانافزار در خط لوله پردازش درخواستها
app.UseResponseCompression();
app.MapGet("/Hello", () => "The quick brown fox jumped over the lazy dog");
app.Run();Accept-Encoding نشان دهد:Content-Encoding خواهد بود و دادهها خام انتقال مییابند.Accept-Encoding: zstdContent-Encoding: zstdAccept-Encoding: potato) ارسال کند، سرور درخواست را نادیده گرفته و پاسخ را بدون فشردهسازی ارسال میکند.Brotli یا Gzip استفاده کنید.using Microsoft.AspNetCore.ResponseCompression;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCompression(options =>
{
// 1. پاکسازی تمام ارائهدهندگان پیشفرض (شامل Zstandard، Brotli و GZip)
options.Providers.Clear();
// 2. افزودن مجدد صرفاً ارائهدهندههای مدنظر
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
var app = builder.Build();
app.UseResponseCompression();
app.MapGet("/Hello", () => "The quick brown fox jumped over the lazy dog");
app.Run();Accept-Encoding: zstd را ارسال کند، سرور به دلیل عدم برخورداری از Provider مربوطه، پاسخ را فشرده نکرده یا در صورت تعریف گزینههای جایگزین در هدر کلاینت، از Brotli یا GZip استفاده خواهد کرد.ASP.NET Core و کلاینت پیشفرض HttpClient همراه شده است. امکان سوئیچ هوشمند بین الگوریتمها و تعریف Fallback در کلاینت در کنار پیکربندی بدون دردسر در سمت سرور، نشاندهنده بلوغ فوقالعاده این ابزار جدید و آماده بودن آن برای بهینهسازی پروژههای مقیاسبزرگ تجاری است.System.Net.Http، سه رَپر (HttpContent Wrapper) اختصاصی برای فشردهسازی بلادرنگ دادههای ارسالی به کلاینت اضافه شده است:GZipCompressedContentBrotliCompressedContentZstandardCompressedContentContent-Encoding مناسب (مانند gzip، br یا zstd) را بهصورت خودکار تنظیم کرده و مقدار Content-Length را حذف میکنند تا فشردهسازی بهصورت جریانی (Streaming) انجام شود.CompressionLevel برای تنظیم ساده بین سرعت و نسبت فشردهسازی.ZLibCompressionOptions، BrotliCompressionOptions و ZstandardCompressionOptions) برای کنترل دقیقتر پارامترها.ZstandardCompressedContent، الزامات استاندارد RFC 9659 برای تعیین و محدودسازی اندازه پنجره حافظه (Window-size) بهصورت خودکار رعایت میشود.HttpClient به هیچ عنوان با سرور درباره فشردهسازی بدنه درخواست مذاکره (Content Negotiation) نمیکند. بنابراین فقط در صورتی باید از این قابلیت استفاده کرد که از پشتیبانی سرور مقصد از انکودینگ مدنظر اطمینان داشته باشید.Buffering) جلوگیری میکند.using System.IO.Compression;
using System.Net.Http;
using var client = new HttpClient();
var payload = new StringContent("""{"greeting":"hello"}""");
using var request = new HttpRequestMessage(HttpMethod.Post, "https://example.com/api")
{
Content = new ZstandardCompressedContent(payload, CompressionLevel.Optimal),
};
using HttpResponseMessage response = await client.SendAsync(request);