أوه لا! علم بياناتي يصدأ

مرحبا يا هبر!



نلفت انتباهكم إلى ترجمة بحث مثير للاهتمام من شركة Crowdstrike. تم تخصيص المادة لاستخدام لغة Rust في مجال علوم البيانات (فيما يتعلق بتحليل البرامج الضارة) وتوضح كيف يمكن لـ Rust المنافسة في مثل هذا المجال حتى مع NumPy و SciPy ، ناهيك عن Python النقي .





استمتع بالقراءة!



Python هي واحدة من أكثر لغات برمجة علوم البيانات شيوعًا ، ولسبب وجيه. يحتوي فهرس حزمة Python (PyPI) على الكثير من مكتبات علوم البيانات المثيرة للإعجاب مثل NumPy و SciPy و Natural Language Toolkit و Pandas و Matplotlib. مع وفرة من مكتبات التحليلات عالية الجودة المتاحة ومجتمع مطور واسع ، فإن Python هو الخيار الواضح للعديد من علماء البيانات.



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



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



يعد تحليل البرامج الضارة أمرًا خطيرًا بطبيعته. غالبًا ما تتعامل البرامج الضارة مع هياكل بيانات تنسيق الملف بطرق غير مقصودة ، مما يؤدي إلى تعطيل أدوات التحليلات. مأزق شائع نسبيًا ينتظرنا في Python هو الافتقار إلى سلامة النوع الجيد. يمكن لـ Python ، التي تقبل القيم بكرم Noneعند توقعها في مكانها bytearray، أن تنزلق إلى حالة من الفوضى الكاملة ، والتي لا يمكن تجنبها إلا من خلال حشو الشفرة بشيكات None. غالبًا ما تؤدي افتراضات "كتابة البط" إلى حدوث أعطال.



ولكن هناك صدأ. يتم وضع الصدأ بطرق عديدة كحل مثالي لجميع المشاكل المحتملة الموضحة أعلاه: يمكن مقارنة وقت التشغيل واستهلاك الذاكرة بـ C و C ++ ، ويتم توفير أمان شامل من النوع. يوفر Rust أيضًا وسائل راحة إضافية ، مثل ضمانات سلامة الذاكرة القوية وعدم وجود حمل وقت التشغيل. نظرًا لعدم وجود مثل هذه النفقات العامة ، فإنه يسهل دمج رمز Rust مع التعليمات البرمجية من لغات أخرى ، خاصة Python. في هذه المقالة ، سنقوم بجولة سريعة في Rust لمعرفة ما إذا كان الأمر يستحق الضجيج المرتبط به.



تطبيق نموذجي لعلوم البيانات



علم البيانات هو موضوع واسع للغاية مع العديد من الجوانب التطبيقية ، ومن المستحيل مناقشة كل منهم في مقال واحد. تتمثل مهمة بسيطة لعلم البيانات في حساب الكون الإعلامي لتسلسلات البايت. يتم تقديم صيغة عامة لحساب الإنتروبيا في البتات على ويكيبيديا :







لحساب الإنتروبيا لمتغير عشوائي X، نحسب أولاً عدد المرات التي تحدث فيها كل قيمة بايت ممكنة ، ثم نقسم هذا العدد على العدد الإجمالي للعناصر التي تمت مواجهتها لحساب احتمالية مواجهة قيمة معينة ، على التوالي . ثم نحسب القيمة السالبة من المجموع المرجح لاحتمال حدوث قيمة معينة xi ، وكذلك ما يسمى بالمعلومات الخاصة... نظرًا لأننا نحسب الإنتروبيا في البتات ، يتم استخدامه هنا (لاحظ الجذر 2 من أجل البتات).



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



بيثون النقي



لنبدأ بوظيفة Python نقية بسيطة (c entropy.py) لحساب الإنتروبيا bytearray، باستخدام وحدة الرياضيات فقط من المكتبة القياسية. هذه الوظيفة غير محسنة ، لذا دعنا نعتبرها نقطة بداية للتعديلات وقياسات الأداء.



import math
def compute_entropy_pure_python(data):
    """Compute entropy on bytearray `data`."""
    counts = [0] * 256
    entropy = 0.0
    length = len(data)
    for byte in data:
        counts[byte] += 1
    for count in counts:
        if count != 0:
            probability = float(count) / length
            entropy -= probability * math.log(probability, 2)
    return entropy


بايثون مع NumPy و SciPy



ليس من المستغرب أن SciPy يوفر وظيفة لحساب الكون. ولكن أولاً ، سنستخدم دالة unique()من NumPy لحساب ترددات البايت. إن مقارنة أداء وظيفة إنتروبيا SciPy مع تطبيقات أخرى أمر غير عادل إلى حد ما ، لأن تطبيق SciPy له وظائف إضافية لحساب الإنتروبيا النسبية (مسافة Kullback-Leibler). مرة أخرى ، سنقوم بإجراء اختبار قيادة (نأمل ألا يكون بطيئًا جدًا) لمعرفة أداء مكتبات Rust المجمعة المستوردة من Python. سنلتزم بتنفيذ SciPy المضمّن في برنامجنا النصي entropy.py.



import numpy as np
from scipy.stats import entropy as scipy_entropy
def compute_entropy_scipy_numpy(data):
    """  bytearray `data`  SciPy  NumPy."""
    counts = np.bincount(bytearray(data), minlength=256)
    return scipy_entropy(counts, base=2)


بيثون مع الصدأ



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



cargo new --lib rust_entropy
Cargo.toml


نبدأ بملف بيان إلزامي Cargo.tomlيحدد حزمة الشحن ويحدد اسم المكتبة rust_entropy_lib. نستخدم حاوية cpython العامة (v0.4.1) المتاحة من crates.io ، في Rust Package Registry. بالنسبة لهذه المقالة ، نستخدم Rust v1.42.0 ، أحدث إصدار ثابت متوفر في وقت كتابة هذا التقرير.



[package] name = "rust-entropy"
version = "0.1.0"
authors = ["Nobody <nobody@nowhere.com>"] edition = "2018"
[lib] name = "rust_entropy_lib"
crate-type = ["dylib"]
[dependencies.cpython] version = "0.4.1"
features = ["extension-module"]


lib.rs



تطبيق مكتبة Rust بسيط جدًا. كما هو الحال مع تطبيق Python الخالص ، نقوم بتهيئة مجموعة التعداد لكل قيمة بايت ممكنة ونكررها على البيانات لتعبئة التعدادات. لإكمال العملية ، قم بحساب وإرجاع المجموع السلبي للاحتمالات مضروبًا في الاحتمالات.



use cpython::{py_fn, py_module_initializer, PyResult, Python};
///    
fn compute_entropy_pure_rust(data: &[u8]) -> f64 {
    let mut counts = [0; 256];
    let mut entropy = 0_f64;
    let length = data.len() as f64;
    // collect byte counts
    for &byte in data.iter() {
        counts[usize::from(byte)] += 1;
    }
    //  
    for &count in counts.iter() {
        if count != 0 {
            let probability = f64::from(count) / length;
            entropy -= probability * probability.log2();
        }
    }
    entropy
}


كل ما تبقى لدينا lib.rsهو آلية لاستدعاء وظيفة الصدأ النقي من Python. نقوم بتضمين وظيفة lib.rsمضبوطة CPython (compute_entropy_cpython())لاستدعاء وظيفة الصدأ "الصرفة" (compute_entropy_pure_rust()). من خلال القيام بذلك ، نحن نستفيد فقط من الحفاظ على تنفيذ واحد فقط من الصدأ وتوفير غلاف صديق CPython.



///  Rust    CPython 
fn compute_entropy_cpython(_: Python, data: &[u8]) -> PyResult<f64> {
    let _gil = Python::acquire_gil();
    let entropy = compute_entropy_pure_rust(data);
    Ok(entropy)
}
//   Python    Rust    CPython 
py_module_initializer!(
    librust_entropy_lib,
    initlibrust_entropy_lib,
    PyInit_rust_entropy_lib,
    |py, m | {
        m.add(py, "__doc__", "Entropy module implemented in Rust")?;
        m.add(
            py,
            "compute_entropy_cpython",
            py_fn!(py, compute_entropy_cpython(data: &[u8])
            )
        )?;
        Ok(())
    }
);


استدعاء رمز الصدأ من Python



أخيرًا ، نسمي تنفيذ Rust من Python (مرة أخرى من entropy.py). للقيام بذلك ، نقوم أولاً باستيراد مكتبة النظام الديناميكي الخاصة بنا التي تم تجميعها من Rust. ثم ، نسمي ببساطة وظيفة المكتبة المقدمة التي حددناها سابقًا عند تهيئة وحدة Python باستخدام ماكرو py_module_initializer!في شفرة Rust الخاصة بنا. في هذه المرحلة ، لدينا وحدة Python ( entropy.py) واحدة فقط ، والتي تتضمن وظائف لاستدعاء جميع تطبيقات حساب الإنتروبيا.



import rust_entropy_lib
def compute_entropy_rust_from_python(data):
    ""  bytearray `data`   Rust."""
    return rust_entropy_lib.compute_entropy_cpython(data)


نحن نبني حزمة مكتبة Rust أعلاه على Ubuntu 18.04 باستخدام Cargo. (قد يكون هذا الرابط مفيدًا لمستخدمي OS X).



cargo build --release


عند الانتهاء من التجميع ، نقوم بإعادة تسمية المكتبة الناتجة ونسخها إلى الدليل حيث توجد وحدات Python الخاصة بنا ، بحيث يمكن استيرادها من البرامج النصية. تمت تسمية المكتبة التي أنشأتها باستخدام Cargo librust_entropy_lib.so، ولكن يجب إعادة تسميتها rust_entropy_lib.soحتى تتمكن من الاستيراد بنجاح كجزء من هذه الاختبارات.



فحص الأداء: النتائج



قمنا بقياس أداء كل وظيفة باستخدام نقاط توقف pytest ، وحساب الإنتروبيا لأكثر من مليون بايت عشوائي. يتم عرض جميع عمليات التنفيذ على نفس البيانات. المقاييس (مدرجة أيضًا في entropy.py) موضحة أدناه.



# ###   ###
#      w/ NumPy
NUM = 1000000
VAL = np.random.randint(0, 256, size=(NUM, ), dtype=np.uint8)
def test_pure_python(benchmark):
    """  Python."""
    benchmark(compute_entropy_pure_python, VAL)
def test_python_scipy_numpy(benchmark):
    """  Python  SciPy."""
    benchmark(compute_entropy_scipy_numpy, VAL)
def test_rust(benchmark):
    """  Rust,   Python."""
    benchmark(compute_entropy_rust_from_python, VAL)


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



import entropy
with open('testdata.bin', 'rb') as f:
    DATA = f.read()
for _ in range(100):
    entropy.compute_entropy_pure_python(DATA)


أظهرت تطبيقات كل من SciPy / NumPy و Rust أداءً جيدًا ، حيث تغلبت بسهولة على تنفيذ Python النقي غير المحسن بأكثر من 100 مرة. كان أداء Rust أفضل قليلاً من إصدار SciPy / NumPy ، لكن النتائج أكدت توقعاتنا: Python النقي أبطأ بكثير من اللغات المترجمة ، ويمكن أن تتنافس الإضافات المكتوبة بلغة Rust مع نظيراتها C بنجاح (التغلب عليها حتى في مثل هذه الاختبار المصغر).



هناك طرق أخرى لتحسين الإنتاجية أيضًا. يمكننا استخدام وحدات ctypesأو cffi. يمكنك إضافة تلميحات كتابة واستخدام Cython لإنشاء مكتبة يمكنك استيرادها من Python. تتطلب جميع هذه الخيارات أن يتم النظر في المقايضات الخاصة بالحلول.







قمنا أيضًا بقياس استخدام الذاكرة لكل عملية تنفيذ باستخدام تطبيق GNU time(يجب عدم الخلط بينه وبين أمر shell المدمج time). على وجه الخصوص ، قمنا بقياس الحد الأقصى لحجم مجموعة المقيمين.



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







النتيجة



لقد تأثرنا للغاية بالأداء الذي نحصل عليه عند الاتصال بـ Rust من Python. في تقييمنا الموجز بصراحة ، كان تنفيذ Rust قادرًا على المنافسة في الأداء مع تنفيذ C الأساسي من حزم SciPy و NumPy. يبدو أن الصدأ رائع في المعالجة الفعالة على نطاق واسع.



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



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



نحن لا ندعو إلى نقل SciPy أو NumPy إلى Rust ، لأن مكتبات Python هذه تم تحسينها بالفعل ودعمها بشكل جيد من قبل مجتمعات مطوري البرامج الرائعة. من ناحية أخرى ، نوصي بشدة بنقل التعليمات البرمجية من Python إلى Rust غير المتوفرة في المكتبات عالية الأداء. في سياق تطبيقات علوم البيانات المستخدمة في التحليل الأمني ​​، يبدو Rust بمثابة بديل تنافسي لـ Python ، نظرًا لسرعته وضماناته الأمنية.



All Articles