عنوان:

‫کاهش مصرف حافظه HttpClient در دات‌نت 10


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۲/۰۹ ۱۲:۳۴
آدرس: www.dntips.ir
مقدمه
بهینه‌سازی مصرف حافظه (Memory Consumption) یکی از عوامل کلیدی در بهبود عملکرد برنامه‌ها و افزایش مقیاس‌پذیری (Scalability) آن‌هاست. در چارچوب دات‌نت، کلاس HttpClient به عنوان یکی از ابزارهای اصلی برای ارسال درخواست‌های اچ‌تی‌تی‌پی (HTTP Requests) و دریافت پاسخ‌ها، نقش مهمی در توسعه برنامه‌های مبتنی بر وب ایفا می‌کند. با این حال، مدیریت نادرست این کلاس می‌تواند منجر به مصرف غیرضروری حافظه و کاهش کارایی شود. در نسخه جدید دات‌نت 10، بهبودهای قابل توجهی در کلاس HttpClient اعمال شده است که به طور خاص بر کاهش مصرف حافظه در سناریوهای استریمینگ (Streaming) تمرکز دارند. این مقاله به بررسی این بهبودها، مکانیزم‌های فنی آن‌ها، و تأثیراتشان بر عملکرد برنامه‌ها می‌پردازد.

1. چالش‌های مدیریت حافظه در HttpClient
کلاس HttpClient برای ارسال درخواست‌های اچ‌تی‌تی‌پی و دریافت پاسخ‌ها از منابعی که با یک آدرس یو‌آر‌آی (URI) شناسایی می‌شوند، طراحی شده است. این کلاس به دلیل استفاده گسترده در برنامه‌های وب و سرویس‌های ابری، باید به طور کارآمد منابع سیستمی را مدیریت کند. یکی از چالش‌های اصلی در نسخه‌های قبلی دات‌نت، مدیریت بافر (Buffer) در هنگام استریمینگ پاسخ‌هایی بود که طول محتوای آن‌ها (Content Length) از قبل مشخص نبود. در چنین سناریوهایی، بافر ممکن بود چندین بار تغییر اندازه دهد (Resize)، که این فرآیند به تخصیص مکرر حافظه (Memory Allocation) و افزایش فشار بر جمع‌آوری زباله (Garbage Collection) منجر می‌شد. این مشکل به ویژه در پاسخ‌های بزرگ، مانند دانلود فایل‌های چند مگابایتی یا استریمینگ داده‌های حجیم، مشهود بود.
برای درک بهتر، دو نوع پاسخ اچ‌تی‌تی‌پی را در نظر بگیرید:
  • پاسخ با طول مشخص: هدر Content-Length در پاسخ وجود دارد و اندازه داده‌ها از قبل معلوم است. در این حالت، HttpClient می‌تواند بافر را با اندازه مناسب تخصیص دهد.
  • پاسخ بدون طول مشخص: هدر Content-Length غایب است، مانند سناریوهای استریمینگ یا پاسخ‌های تولیدشده به صورت پویا. در این موارد، بافر ممکن است بارها تغییر اندازه دهد، که باعث افزایش مصرف حافظه و کاهش عملکرد می‌شود.

2. بهبودهای HttpClient در دات‌نت 10
در دات‌نت 10، تیم توسعه‌دهندگان مایکروسافت با معرفی یک رویکرد مبتنی بر استخر بافر (Buffer Pooling) این چالش را برطرف کرده‌اند. این رویکرد به جای تخصیص بافرهای جدید برای هر پاسخ، از یک مجموعه بافرهای از پیش تخصیص‌یافته استفاده می‌کند که می‌توانند بین درخواست‌ها به اشتراک گذاشته شوند. این مکانیزم به طور قابل توجهی سربار (Overhead) ناشی از تخصیص حافظه را کاهش می‌دهد و عملکرد را بهبود می‌بخشد.

2.1. جزئیات فنی بهبودها
بر اساس اطلاعات منتشرشده در مخزن گیت‌هاب دات‌نت، بهبودهای اعمال‌شده در HttpClient به طور خاص در متدهایی مانند GetAsync و GetByteArrayAsync قابل مشاهده است. این متدها برای دریافت پاسخ‌های اچ‌تی‌تی‌پی استفاده می‌شوند و در دات‌نت 10 بهینه‌سازی‌هایی در مصرف حافظه و سرعت اجرا دریافت کرده‌اند. جدول زیر، که از یک درخواست کشش (Pull Request) در گیت‌هاب استخراج شده، عملکرد این متدها را قبل و بعد از بهبودها مقایسه می‌کند:
متدنسخهطول داده (بایت)میانگین زمان (نانوثانیه)نسبتحافظه تخصیص‌یافته (کیلو‌بایت)نسبت تخصیص
GetByteArrayAsyncMain10,0001,114.61.0010.811.00
GetByteArrayAsyncPR10,000994.30.8910.841.00
GetAsyncMain10,0001,284.71.0028.931.00
GetAsyncPR10,0001,018.10.7910.840.37
GetByteArrayAsyncMain1,000,000127,633.71.00978.161.00
GetByteArrayAsyncPR1,000,000110,941.50.87978.111.00
GetAsyncMain1,000,000151,716.71.002,032.281.00
GetAsyncPR1,000,000110,231.70.73978.10.48
GetAsyncMain100,000,00025,177,538.41.00260,446.051.00
GetAsyncPR100,000,00016,487,114.60.6697,657.690.37

تحلیل جدول:
  • کاهش زمان اجرا: متد GetAsync در سناریوهای با داده‌های بزرگ (مانند 100 مگابایت) تا 34 درصد سریع‌تر عمل می‌کند (نسبت 0.66).
  • کاهش تخصیص حافظه: در همان سناریو، مصرف حافظه در متد GetAsync تا 63 درصد کاهش یافته است (نسبت تخصیص 0.37).
  • تأثیر در GetByteArrayAsync: اگرچه بهبودها در این متد کمتر چشمگیر هستند، اما همچنان کاهش قابل توجهی در زمان اجرا (تا 20 درصد برای داده‌های 100 مگابایتی) مشاهده می‌شود.
این بهبودها به لطف استفاده از استخر بافر و بهینه‌سازی مدیریت حافظه در پیاده‌سازی جدید HttpClient محقق شده‌اند.

2.2. مثال عملی
برای درک بهتر تأثیر این بهبودها، یک نمونه کد ساده را در نظر بگیرید که از HttpClient برای دانلود یک فایل بزرگ استفاده می‌کند:
using System.Net.Http;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        using var client = new HttpClient();
        var response = await client.GetAsync("https://example.com/largefile", HttpCompletionOption.ResponseHeadersRead);
        using var stream = await response.Content.ReadAsStreamAsync();
        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            // پردازش داده‌ها
        }
    }
}
در این کد، متد GetAsync با گزینه HttpCompletionOption.ResponseHeadersRead استفاده شده است تا پاسخ به صورت استریمینگ دریافت شود. در دات‌نت 10، بافرهای مورد استفاده در فرآیند استریمینگ از یک استخر بافر مدیریت‌شده تأمین می‌شوند، که باعث کاهش تخصیص‌های مکرر حافظه و بهبود عملکرد می‌شود.

3. ارتباط با سایر بهینه‌سازی‌ها
بهبودهای HttpClient در دات‌نت 10 بخشی از تلاش گسترده‌تر مایکروسافت برای بهینه‌سازی عملکرد چارچوب دات‌نت است. برای مثال، در نسخه‌های قبلی (مانند دات‌نت 8 و 9)، شاهد معرفی متریک‌های داخلی (Built-in Metrics) برای نظارت بر عملکرد HttpClient و بهبودهای مربوط به اتصال چندگانه اچ‌تی‌تی‌پی/3 (HTTP/3) بودیم. این تغییرات، در کنار بهینه‌سازی‌های دات‌نت 10، امکان ساخت برنامه‌های مقیاس‌پذیرتر و کارآمدتر را فراهم می‌کنند.
علاوه بر این، استفاده از IHttpClientFactory همچنان به عنوان بهترین روش برای مدیریت نمونه‌های HttpClient توصیه می‌شود. این کارخانه (Factory) با مدیریت چرخه حیات (Lifetime Management) نمونه‌ها و استفاده از تنظیماتی مانند PooledConnectionLifetime، به توسعه‌دهندگان کمک می‌کند تا از مشکلات مرتبط با تغییرات دی‌ان‌اس (DNS Changes) و مصرف بیش از حد پورت‌ها جلوگیری کنند.

4. توصیه‌های عملی برای توسعه‌دهندگان
برای بهره‌مندی کامل از بهبودهای HttpClient در دات‌نت 10، توسعه‌دهندگان باید به نکات زیر توجه کنند:
  • استفاده از استریمینگ برای داده‌های بزرگ: از متدهایی مانند GetAsync با گزینه HttpCompletionOption.ResponseHeadersRead برای استریمینگ پاسخ‌های بزرگ استفاده کنید.
  • پیکربندی مناسب HttpClient: اطمینان حاصل کنید که نمونه‌های HttpClient به درستی پیکربندی شده‌اند و از بازیافت (Pooling) مناسب برای کاهش سربار استفاده می‌شود.
  • نظارت بر عملکرد: از متریک‌های داخلی دات‌نت برای نظارت بر عملکرد درخواست‌های اچ‌تی‌تی‌پی استفاده کنید تا گلوگاه‌های احتمالی را شناسایی کنید.
  • آزمایش در سناریوهای واقعی: بهبودهای ذکرشده را در محیط‌های واقعی آزمایش کنید تا تأثیر آن‌ها بر برنامه‌های خاص خود را ارزیابی کنید.

نتیجه‌گیری
دات‌نت 10 با معرفی بهینه‌سازی‌های قابل توجه در کلاس HttpClient، گام مهمی در جهت کاهش مصرف حافظه و بهبود عملکرد برنامه‌های مبتنی بر وب برداشته است. رویکرد جدید مبتنی بر استخر بافر، به ویژه در سناریوهای استریمینگ بدون طول مشخص، سربار تخصیص حافظه را به طور قابل توجهی کاهش داده و زمان اجرا را بهبود بخشیده است. این تغییرات، همراه با سایر بهینه‌سازی‌های چارچوب دات‌نت، توسعه‌دهندگان را قادر می‌سازد تا برنامه‌هایی مقیاس‌پذیرتر و کارآمدتر بسازند. با به‌کارگیری بهترین روش‌ها، مانند استفاده از IHttpClientFactory و پیکربندی مناسب نمونه‌های HttpClient، توسعه‌دهندگان می‌توانند از مزایای کامل این بهبودها بهره‌مند شوند.


مشاهده مطلب اصلی