تحميل صفيف NumPy من القرص: مقارنة memmap () و Zarr / HDF5

إذا كان صفيف NumPy الخاص بك كبيرًا جدًا بحيث لا يتناسب مع ذاكرة الوصول العشوائي ، يمكنك معالجته عن طريق تقسيمه إلى أجزاء . يمكنك القيام بذلك إما في الوضع الشفاف أو بشكل صريح عن طريق تحميل هذه الأجزاء من القرص في وقت واحد. في هذه الحالة ، يمكنك اللجوء إلى فئتين من الأدوات:





  • طريقة NumPy memmap()، آلية شفافة تسمح لك برؤية ملف موجود على القرص كما لو كان في الذاكرة بالكامل. 
  • تنسيقات تخزين البيانات Zarr و HDF5 المتشابهة مع بعضها البعض ، والتي تسمح ، إذا لزم الأمر ، بالتحميل من القرص وحفظ أجزاء مضغوطة من الصفيف إلى القرص.

كل من هذه الأساليب لها نقاط القوة والضعف الخاصة بها. 

المادة ، التي ننشر ترجمتها اليوم ، مخصصة لتحليل ميزات هذه الأساليب للعمل مع البيانات ، وقصة المواقف التي قد تكون فيها في متناول اليد. على وجه الخصوص ، سيتم إيلاء اهتمام خاص لتنسيقات البيانات التي تم تحسينها لأداء العمليات الحسابية وليست بالضرورة مصممة لنقل هذه البيانات إلى المبرمجين الآخرين.

ماذا يحدث عند قراءة البيانات من القرص أو كتابة البيانات على القرص؟


عند قراءة ملف من القرص لأول مرة ، لا يقوم نظام التشغيل بنسخ البيانات إلى ذاكرة العملية فقط. أولاً ، تقوم بنسخ هذه البيانات في ذاكرتها ، وتخزين نسخة منها في ما يسمى "ذاكرة التخزين المؤقت العازلة".

ما الفائدة هنا؟

والحقيقة هي أن نظام التشغيل يخزن البيانات في ذاكرة التخزين المؤقت في حالة احتياجك لقراءة نفس البيانات من نفس الملف مرة أخرى.


إذا تمت قراءة البيانات مرة أخرى ، فإنها تدخل ذاكرة البرنامج ليس من القرص ، ولكن من ذاكرة الوصول العشوائي ، وهي أوامر بحجم أسرع.


إذا كانت الذاكرة التي تشغلها ذاكرة التخزين المؤقت مطلوبة لشيء آخر ، فسيتم مسح ذاكرة التخزين المؤقت تلقائيًا.

عندما تتم كتابة البيانات على القرص ، فإنها تتحرك في الاتجاه المعاكس. في البداية يتم كتابتها فقط إلى ذاكرة التخزين المؤقت. وهذا يعني أن عمليات الكتابة عادة ما تكون سريعة جدًا ، حيث لا يحتاج البرنامج إلى التركيز على قرص بطيء. تحتاج أثناء التسجيل إلى العمل فقط مع ذاكرة الوصول العشوائي.


ونتيجة لذلك ، يتم مسح البيانات إلى القرص من ذاكرة التخزين المؤقت.


العمل مع صفيف باستخدام memmap ()


في حالتنا ، memmap()يسمح لنا بإدراك ملف على القرص كما لو كان صفيفًا مخزنًا في الذاكرة. يقوم نظام التشغيل ، الشفاف للبرنامج ، بإجراء عمليات قراءة / كتابة ، والوصول إلى ذاكرة التخزين المؤقت المؤقت أو القرص الثابت ، اعتمادًا على ما إذا كانت البيانات المطلوبة مخزنة مؤقتًا في الذاكرة أم لا. يتم تنفيذ خوارزمية مثل هذه هنا:

  • هل البيانات في ذاكرة التخزين المؤقت؟ إذا كان الأمر كذلك - عظيم - يمكنك الاتصال بهم مباشرة.
  • هل البيانات الموجودة على القرص؟ سيكون الوصول إليها أبطأ ، ولكن لا داعي للقلق حيال ذلك ، سيتم تحميلها في وضع شفاف.

كإضافة إضافية ، memmap()يمكن ملاحظة أنه في معظم الحالات سيتم تخزين ذاكرة التخزين المؤقت للملف في ذاكرة البرنامج. وهذا يعني أن النظام لا يحتاج إلى الاحتفاظ بنسخة إضافية من البيانات في ذاكرة البرنامج خارج المخزن المؤقت.


الطريقة مضمنة memmap()في NumPy:

import numpy as np
array = np.memmap("mydata/myarray.arr", mode="r",
                  dtype=np.int16, shape=(1024, 1024))

قم بتشغيل هذا الرمز ، وسيكون لديك صفيف تحت تصرفك ، وسيكون العمل شفافًا تمامًا للبرنامج - بغض النظر عما إذا كان العمل يتم باستخدام ذاكرة التخزين المؤقت أو مع القرص الثابت.

قيود Memmap ()


على الرغم من أنه في حالات معينة memmap()يمكن أن تظهر نفسها بشكل جيد ، إلا أن هذه الطريقة لها أيضًا قيود:

  • يجب تخزين البيانات في نظام الملفات. لا يمكن تنزيل البيانات من التخزين الثنائي مثل AWS S3.
  • , . , . , , , .
  • N- , , , . .

دعونا نشرح النقطة الأخيرة. تخيل أن لدينا مصفوفة ثنائية الأبعاد تحتوي على أعداد صحيحة 32 بت (4 بايت). تتم قراءة 4096 بايت لكل قرص. إذا قرأت البيانات الموجودة في ملف بالتتابع من قرص (على سبيل المثال ، هذه البيانات موجودة في سطور المصفوفة) ، فبعد كل عملية قراءة ، سيكون لدينا 1024 عددًا صحيحًا. ولكن إذا قرأت البيانات التي لا يتطابق موقعها في الملف مع موقعها في المصفوفة (على سبيل المثال ، البيانات الموجودة في الأعمدة) ، فستسمح لك كل عملية قراءة بالحصول على رقم واحد ضروري فقط. ونتيجة لذلك ، اتضح أنه للحصول على نفس الكمية من البيانات ، يجب عليك إجراء عمليات قراءة أكثر ألف مرة.

Zarr و HDF5


من أجل التغلب على القيود المذكورة أعلاه ، يمكنك استخدام تنسيقات تخزين البيانات Zarr أو HDF5 ، وهي متشابهة جدًا:

  • يمكنك العمل مع ملفات HDF5 في Python باستخدام pytables أو h5py . هذا التنسيق أقدم من Zarr ولديه قيود أكثر ، ولكن زائده هو أنه يمكن استخدامه في البرامج المكتوبة بلغات مختلفة.
  • Zarr هو تنسيق يتم تنفيذه باستخدام حزمة Python التي تحمل الاسم نفسه. إنه أكثر حداثة ومرونة من HDF5 ، ولكن يمكنك استخدامه (على الأقل في الوقت الحالي) فقط في بيئة Python. وفقًا لمشاعري ، في معظم الحالات ، إذا لم تكن هناك حاجة إلى دعم متعدد اللغات لـ HDF5 ، فمن الجدير اختيار Zarr. Zarr ، على سبيل المثال ، لديها دعم متعدد أفضل.

علاوة على ذلك ، سنناقش فقط Zarr ، ولكن إذا كنت مهتمًا بتنسيق HDF5 ومقارنته الأعمق مع Zarr ، فيمكنك مشاهدة هذا الفيديو.

باستخدام زر


يسمح لك Zarr بتخزين أجزاء من البيانات وتحميلها في الذاكرة على شكل صفائف ، وكذلك - كتابة هذه البيانات في شكل صفائف.

إليك كيفية تحميل مصفوفة باستخدام Zarr:

>>> import zarr, numpy as np
>>> z = zarr.open('example.zarr', mode='a',
...               shape=(1024, 1024),
...               chunks=(512,512), dtype=np.int16)
>>> type(z)
<class 'zarr.core.Array'>
>>> type(z[100:200])
<class 'numpy.ndarray'>

يرجى ملاحظة أنه حتى يتم استلام شريحة من الكائن ، فلن نكون تحت تصرفنا numpy.ndarray. الكيان zarr.core.arrayهو مجرد بيانات وصفية. يتم تحميل البيانات المضمنة في الشريحة فقط من القرص.

لماذا اخترت زار؟


  • زار يتحايل على القيود memmap()المذكورة أعلاه:
  • يمكن تخزين أجزاء البيانات على القرص أو في تخزين AWS S3 أو في بعض أنظمة التخزين التي توفر القدرة على العمل مع سجلات تنسيق المفتاح / القيمة.
  • يتم تحديد حجم وهيكل جزء البيانات من قبل المبرمج. على سبيل المثال ، يمكن تنظيم البيانات بطريقة تمكنها من قراءة المعلومات الموجودة بكفاءة على محاور مختلفة لصفيف متعدد الأبعاد. وينطبق هذا على HDF5.
  • يمكن ضغط الأجزاء. ويمكن قول الشيء نفسه عن HDF5.

دعونا نتطرق إلى النقطتين الأخيرتين بمزيد من التفصيل.

أبعاد الشظية


لنفترض أننا نعمل مع مجموعة من 30.000 × 3000 عنصر في الحجم. إذا كنت بحاجة إلى قراءة هذا الصفيف والانتقال على طول محوره X، والانتقال على طول محوره Y، يمكنك حفظ الأجزاء التي تحتوي على بيانات هذا الصفيف ، كما هو موضح أدناه (في الممارسة العملية ، على الأرجح ، ستحتاج إلى أكثر من 9 أجزاء):


الآن ، يمكن تحميل البيانات الموجودة على كل من المحور Xوالمحور Yبكفاءة. بناءً على نوع البيانات المطلوبة في البرنامج ، يمكنك تنزيل الأجزاء ، على سبيل المثال ، الأجزاء (1 ، 0) ، (1 ، 1) ، (1 ، 2) ، أو الأجزاء (0 ، 1) ، (1 ، 1) ، (2 ، 1).

ضغط البيانات


يمكن ضغط كل جزء. هذا يعني أن البيانات يمكن أن تدخل البرنامج بشكل أسرع من القرص يسمح لك بقراءة المعلومات غير المضغوطة. إذا تم ضغط البيانات 3 مرات ، فهذا يعني أنه يمكن تنزيلها من القرص 3 مرات أسرع من البيانات غير المضغوطة ، ناقص الوقت الذي يستغرقه المعالج لفك ضغطه.


بعد تنزيل الأجزاء ، يمكن إزالتها من ذاكرة البرنامج.

ملخص: memmap () أم زار؟


memmap()أيهما أفضل للاستخدام - أم زار؟

Memmap()تبدو مثيرة للاهتمام في مثل هذه الحالات:

  • هناك العديد من العمليات التي تقرأ أجزاء من نفس الملف. memmap()ستتمكن هذه العمليات ، بفضل التطبيق ، من مشاركة نفس ذاكرة التخزين المؤقت. هذا يعني أنه يجب الاحتفاظ بنسخة واحدة فقط من البيانات في الذاكرة ، بغض النظر عن عدد العمليات قيد التشغيل.
  • لا يرغب المطور في إدارة الذاكرة يدويًا. يخطط للاعتماد ببساطة على قدرات نظام التشغيل ، والتي ستحل جميع مشكلات إدارة الذاكرة تلقائيًا وبشكل غير مرئي للمطور.

Zarr مفيد بشكل خاص في المواقف التالية (في بعض الحالات ، كما سيتم ملاحظته ، تنسيق HDF5 قابل للتطبيق أيضًا):

  • يتم تنزيل البيانات من مصادر بعيدة ، وليس من نظام الملفات المحلي.
  • من المحتمل جدًا أن يكون عنق الزجاجة في النظام يقرأ من القرص. سيسمح ضغط البيانات باستخدام أكثر كفاءة لقدرات الأجهزة. ينطبق هذا أيضًا على HDF5.
  • إذا كنت بحاجة إلى الحصول على شرائح من المصفوفات متعددة الأبعاد على طول محاور مختلفة ، فإن Zarr يساعد على تحسين مثل هذه العمليات من خلال تحديد الحجم المناسب وهيكل الأجزاء. وينطبق هذا على HDF5.

سأختار بين memmap()Zarr ، وقبل كل شيء ، أحاول استخدام Zarr - بسبب المرونة التي توفرها هذه الحزمة وتنسيق تخزين البيانات التي تنفذها.

القراء الأعزاء! كيف تحل مشكلة العمل مع صفائف NumPy الكبيرة؟


All Articles