معالجة البيانات الضخمة بلغة C#: من LINQ إلى التدفقات المتوازية

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

لماذا C# هي أداتك السرية لـ Big Data؟

\n\n

عندما يتعلق الأمر بمعالجة البيانات الضخمة، يتبادر إلى الذهن فوراً Python مع Pandas، أو Scala مع Spark، أو Java مع Hadoop. لكن لنكن صادقين: غالباً ما تبقى C# في الظل دون وجه حق. وهذا خطأ. فمنصة .NET تمنحنا أدوات قوية مثل PLINQ وMemoryMappedFile وParallel.For والتدفقات غير المتزامنة، والتي تتيح لنا معالجة جيجابايت من البيانات على أجهزة عادية دون تعقيدات.

\n\n

في هذه المقالة، لن نضيع الوقت في النظريات - سننتقل مباشرة إلى العمل. ستتعلم كيف تستخرج أقصى أداء من معالجك عند معالجة البيانات الضخمة بلغة C#، وكيف تتجنب خطأ OutOfMemoryException، وكيف تعالج ملفات بحجم عشرات الجيجابايت وكأنها مجرد كتاب كبير.

\n\n

المشكلة الرئيسية: كيف لا تقتل الذاكرة؟

\n\n

تخيل: لديك ملف CSV بحجم 50 جيجابايت يحتوي على سجلات الخادم. تفتحه باستخدام StreamReader عادي وتقرأه سطراً سطراً. هل كل شيء على ما يرام؟ لا. إذا حاولت تحميله بالكامل في List<string> - مرحباً بك في OutOfMemoryException. حتى إذا كان لديك 64 جيجابايت من RAM، يمكن لـ .NET تجزئة الذاكرة بحيث لا يتسع الكائن الكبير في الكومة.

\n\n

الحل الأول: التدفق والتقسيم إلى أجزاء. لا تحمل كل شيء دفعة واحدة - امضغه على شكل قطع صغيرة.

\n\n
public async IAsyncEnumerable<string[]> ReadCsvChunksAsync(string filePath, int chunkSize = 10000)\n{\n    using var reader = new StreamReader(filePath);\n    string? line;\n    var buffer = new List<string>(chunkSize);\n\n    while ((line = await reader.ReadLineAsync()) != null)\n    {\n        buffer.Add(line);\n        if (buffer.Count >= chunkSize)\n        {\n            yield return buffer.ToArray();\n            buffer.Clear();\n        }\n    }\n\n    if (buffer.Count > 0)\n        yield return buffer.ToArray();\n}\n
\n\n

الآن يمكنك معالجة الجيجابايت دون القلق بشأن الذاكرة:

\n\n
await foreach (var chunk in ReadCsvChunksAsync("logs.csv"))\n{\n    ProcessChunk(chunk); // منطقك الخاص\n}\n
\n\n

المعالجة المتوازية: استخدام جميع أنوية المعالج

\n\n

حسناً، نقرأ البيانات على دفعات. لكن إذا كان لديك معالج بـ 16 نواة، والبيانات تُعالج بشكل تسلسلي - فأنت تستخدم فقط 6% من طاقته. حان الوقت لتشغيل الحوسبة المتوازية بلغة C#.

\n\n

PLINQ — Parallel LINQ

\n\n

أبسط سحر هو إضافة .AsParallel() إلى استعلام LINQ الخاص بك. لكن مع البيانات الضخمة يجب أن تكون حذراً: PLINQ يقرر بنفسه متى يقوم بالتوازي، وهذا قد يؤدي إلى تجزئة زائدة.

\n\n
var numbers = Enumerable.Range(1, 10_000_000);\n\n// LINQ عادي (بطيء)\nvar sum = numbers.Where(n => n % 2 == 0).Sum();\n\n// PLINQ (سريع، لكن بحذر)\nvar parallelSum = numbers\n    .AsParallel()\n    .WithDegreeOfParallelism(Environment.ProcessorCount)\n    .Where(n => n % 2 == 0)\n    .Sum();\n\nConsole.WriteLine($"النتيجة: {parallelSum}");\n
\n\n

لكن PLINQ ليس حلاً سحرياً. للمعالجة المعقدة للبيانات الضخمة بلغة C#، من الأفضل استخدام Parallel.ForEach مع تحكم يدوي في التقسيم.

\n\n

Parallel.ForEach مع التقسيم إلى نطاقات

\n\n
var allLines = File.ReadLines("huge_dataset.csv");\nvar processedCount = 0;\nvar lockObject = new object();\n\nParallel.ForEach(\n    allLines,\n    new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },\n    line =>\n    {\n        var result = ProcessLine(line); // وظيفتك الثقيلة\n        lock (lockObject)\n        {\n            processedCount++;\n            if (processedCount % 100_000 == 0)\n                Console.WriteLine($"تمت المعالجة: {processedCount}");\n        }\n    });\n
\n\n

MemoryMappedFile: العمل مع الملفات كما لو كانت ذاكرة وصول عشوائي

\n\n

عندما يكون الملف كبيراً جداً بحيث لا يمكن تحميله في RAM، لكنك تحتا

المدونات

توصيات الكتب