لماذا 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\npublic 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\nawait foreach (var chunk in ReadCsvChunksAsync("logs.csv"))\n{\n ProcessChunk(chunk); // منطقك الخاص\n}\n\n\nالمعالجة المتوازية: استخدام جميع أنوية المعالج
\n\nحسناً، نقرأ البيانات على دفعات. لكن إذا كان لديك معالج بـ 16 نواة، والبيانات تُعالج بشكل تسلسلي - فأنت تستخدم فقط 6% من طاقته. حان الوقت لتشغيل الحوسبة المتوازية بلغة C#.
\n\nPLINQ — Parallel LINQ
\n\nأبسط سحر هو إضافة .AsParallel() إلى استعلام LINQ الخاص بك. لكن مع البيانات الضخمة يجب أن تكون حذراً: PLINQ يقرر بنفسه متى يقوم بالتوازي، وهذا قد يؤدي إلى تجزئة زائدة.
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 مع تحكم يدوي في التقسيم.
Parallel.ForEach مع التقسيم إلى نطاقات
\n\nvar 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\nMemoryMappedFile: العمل مع الملفات كما لو كانت ذاكرة وصول عشوائي
\n\nعندما يكون الملف كبيراً جداً بحيث لا يمكن تحميله في RAM، لكنك تحتا