خلفية صغيرة عن Python
بايثون لغة رائعة. جربت عدة لغات قبلها: باسكال في المدرسة. C ، C مع فصول ، C ++ في الجامعة. غرس آخر اثنين (ثلاثة) نفورًا قويًا من البرمجة: بدلاً من حل المشكلة ، فأنت تعبث بالمخصصات والمدمرات (كلمات مخيفة من الماضي) ، تعتقد من حيث البدائية منخفضة المستوى. رأيي أن لغة C ليست مناسبة لحل المشكلات التربوية والعلمية (في أي حال ، في مجال الرياضيات). أنا متأكد من أنهم سيعارضونني ، لكنني لا أحاول فرض أي شيء على أي شخص ، أنا فقط أعبر عن رأيي.
كان بيثون الوحي ذات مرة. لأول مرة في حياتي ، كتبت عدة مستويات تجريدية أعلى من المعتاد في C. تم تقليل المسافة بين المهمة والرمز كما لم يحدث من قبل.
ربما كنت سأفعل هذا طوال حياتي في Python إذا لم يكن عليّ تنفيذ الاختبارات الإحصائية NIST فجأة. يبدو أن المهمة بسيطة للغاية: هناك مجموعة طولها عدة (> = 10) ميغا بايت ، وهناك مجموعة من الاختبارات التي يجب تطبيقها على هذا الصفيف.
ما هو جيد numpy؟
في python ، هناك حزمة جيدة للعمل مع المصفوفات ، وهي في الأساس واجهة عالية المستوى للمكتبات السريعة مثل LAPACK. يبدو أن مجموعة النبلاء الكاملة للحوسبة العلمية متاحة (Sympy ، Numpy ، Scipy ، Matplotlib) ، فما الذي تريده أكثر من ذلك؟
كل من تعامل مع numpy يعلم أنه جيد ، ولكن ليس في كل شيء. نحتاج أيضًا إلى محاولة التأكد من أن العمليات موجهة (كما هو الحال في R) ، أي بمعنى ، موحد لمجموعة كاملة. إذا كانت مشكلتك فجأة لسبب ما لا تتناسب مع هذا النموذج ، فعندئذ لديك مشاكل.
ما نوع المهام غير الموجهة التي أتحدث عنها؟ نعم ، على الأقل خذ نفس NIST: احسب طول سجل التحول الخطي باستخدام خوارزمية Berlekamp-Messi ؛ حساب طول التتابع الأطول للوحدات المتتالية وهكذا. أنا لا أستبعد إمكانية وجود نوع من الحلول المبتكرة التي ستقلل المشكلة إلى حل متجه.
ماكرة؟كمثال من نفس NIST: كان من الضروري حساب عدد تسلسل "التبديل" ، حيث يعني "التبديل" تغيير الوحدات المتتالية (... 1111 ...) إلى أصفار متتالية (... 000 ... ) والعكس صحيح. للقيام بذلك ، يمكنك أخذ التسلسل الأصلي بدون العنصر الأخير (x [: -1]) وطرح منه التسلسل الذي تم تحويله بمقدار 1 (x [2:]) ، ثم حساب عدد العناصر غير الصفرية في التسلسل الجديد الناتج. سيبدو كل ذلك معًا:
np.count_nonzero(x[:-1] - x[1:])
قد يبدو الأمر وكأنه تمرين ترفيهي للعقل ، ولكن هناك شيء غير طبيعي يحدث هنا ، وهي خدعة لن تكون واضحة للقارئ بعد فترة قصيرة. ناهيك عن أن هذا لا يزال بطيئًا - لم يقم أحد بإلغاء تخصيص الذاكرة.
العمليات غير الموجهة في Python هي فترة طويلة. كيف تتعامل معهم إذا لم يكن كافيا؟ عادة ما يقدمون العديد من الحلول:
- نومبا جيت. إذا عملت كما هو موضح في صفحة عنوان Numba ، أعتقد أنه سيكون من المفيد إنهاء القصة هنا. كنت قد نسيت بعض الشيء في الماضي أن الخطأ الذي حدث لها ؛ خلاصة القول هي أن التسارع لم يكن مثيرًا للإعجاب كما توقعت ، للأسف.
- سيثون. حسنًا ، ارفعوا أيديكم أولئك الذين يعتقدون أن cython هو حل جميل وأنيق حقًا لا يدمر معنى وروح Python؟ لا أعتقد ذلك. إذا وصلت إلى Cython ، يمكنك بالفعل التوقف عن خداع نفسك والتحول إلى شيء أقل تعقيدًا مثل C ++ و C.
- أعد كتابة الاختناقات في C وانتزعها من Python المحبوب. حسنًا ، ولكن ماذا لو كان لدي البرنامج بأكمله - كل شيء يتعلق بالحسابات والاختناقات؟ شي لا تقدم! أنا لا أتحدث عن حقيقة أنه في هذا الحل لا تحتاج إلى معرفة لغة واحدة فحسب ، بل لغتين - Python و C.
هنا يأتي جوليا!
بعد التفكير في الحلول الحالية وعدم العثور على أي شيء جيد (غير قادر على البرمجة) ، قررت إعادة كتابته بشيء "أسرع". في الواقع ، إذا كتبت "دراسًا للأرقام" في القرن الحادي والعشرين مع دعم عادي للصفائف والعمليات الموجهة "خارج الصندوق" ، إلخ. وما إلى ذلك ، فإن اختيارك ليس كثيفًا جدًا:
- فورتران . ولا تضحك ، من منا لم يسحب BLAS / LAPACK من لغاتنا المفضلة؟ فورتران هي لغة جيدة حقا (أفضل SI!) للحوسبة العلمية. لم أفهم ذلك لأنه منذ زمن فورتران تم اختراع أشياء كثيرة وإضافتها إلى لغات البرمجة. كنت آمل في شيء أكثر حداثة.
- يعاني R من نفس المشاكل مثل Python (vectorization).
- ماتلاب - ربما نعم ، ليس لديّ أي أموال للتحقق.
- جوليا - الحصان مظلم ، سيقلع ، لن ينطلق (وكان من الطبيعي حتى الإصدار 1.0 المستقر)
بعض مزايا جوليا الواضحة
- يبدو أن Python ، على الأقل نفس "المستوى العالي" ، مع القدرة ، إذا لزم الأمر ، على النزول إلى أجزاء في الأرقام.
- لا ضجة مع تخصيصات الذاكرة وما شابه ذلك.
- نظام نوع قوي. يتم وصف الأنواع اختياريًا ومجرّدًا جدًا. يمكن كتابة البرنامج دون تحديد نوع واحد - وإذا قمت بذلك ، فسيكون قادرًا على القيام بذلك بسرعة. ولكن هناك فروق دقيقة.
- من السهل كتابة أنواع مخصصة ستكون بنفس سرعة الأنواع المدمجة.
- إرسال متعددة. يمكنك التحدث عن هذا لساعات ، ولكن في رأيي - هذا هو أفضل ما تملكه جوليا ، فهو الأساس لتصميم جميع البرامج وبشكل عام أساس فلسفة اللغة.
بفضل الإرسال المتعدد ، تتم كتابة العديد من الأشياء بشكل موحد للغاية.
أمثلة إرسال متعددة rand() # 0 1 rand(10) # 10 0 1 rand(3,5) # 3 5 .... using Distributions d = Normal() # 0, 1 rand(d) # rand(d, 10) # 10 ...
أي أن الوسيطة الأولى يمكن أن تكون أي توزيع (أحادي البعد) من التوزيعات ، لكن استدعاء الوظيفة سيظل حرفياً كما هو. وليس فقط التوزيع (من الممكن إرسال RNG نفسه ككائن MersenneTwister وأكثر من ذلك بكثير). مثال آخر (في رأيي ، توضيحية) هو التنقل في DataFrames بدون loc / iloc.
- 6. المصفوفات هي أصلية ، مدمجة. المتجه من خارج منطقة الجزاء.
سلبيات الصمت حول أي جريمة ستكون!
- لغة جديدة. من ناحية ، بالطبع ، ناقص. في شيء ، ربما غير ناضج. من ناحية أخرى ، أخذ في الاعتبار أشعل النار في العديد من اللغات الماضية ، ويقف "على أكتاف العمالقة" ، وقد استوعب الكثير من الأشياء المثيرة للاهتمام والجديدة.
- من غير المحتمل أن تكتب البرامج السريعة على الفور. هناك بعض الأشياء غير الواضحة التي يسهل التعامل معها: تخطو على أشعل النار ، تطلب المساعدة من المجتمع ، تخطو مرة أخرى ... إلخ. هذه هي بشكل رئيسي عدم استقرار النوع ، وعدم استقرار النوع ، والمتغيرات العالمية. بشكل عام ، بقدر ما أستطيع أن أقول بنفسي ، فإن المبرمج الذي يريد أن يكتب بسرعة في جوليا يمر بعدة مراحل: أ) يكتب في بايثون. هذا رائع ، وهو كذلك ، ولكن في بعض الأحيان ستكون هناك مشاكل في الأداء. ب) يكتب كما في ج: يصف الأنواع يدويًا كلما أمكن ذلك. ج) يتفهم تدريجيًا حيث يكون من الضروري (المقاس جدًا) وصف الأنواع ، وأين تتدخل.
- النظام البيئي بعض الطرود خام ، بمعنى أنه في مكان ما يسقط شيء ما باستمرار ؛ بعضها ناضج ، ولكن غير متناسق مع بعضها البعض (التحويل إلى أنواع أخرى ضروري ، على سبيل المثال). من ناحية ، من الواضح أن هذا سيئ. من ناحية أخرى ، لدى جوليا الكثير من الأفكار المثيرة للاهتمام والجريئة لمجرد أن "نقف على أكتاف العمالقة" - تراكمت لدينا خبرة هائلة في السير على أشعل النار و "كيفية عدم القيام بذلك" ، ويتم أخذ ذلك في الاعتبار (جزئيًا) من قبل مطوري الحزم.
- صفائف ترقيم من 1. ها ، يمزح ، هذا بالطبع زائد! لا ، حسنًا ، على محمل الجد ، ما الأمر ، مع أي مؤشر يبدأ الترقيم؟ تعتاد عليه في 5 دقائق. لا أحد يشكو من أن الأعداد الصحيحة تسمى أعداد صحيحة ، وليس كلها. هذه كلها مسائل ذوق. ونعم ، خذ على الأقل نفس Cormen وفقًا للخوارزميات - كل شيء مرقم من هناك ، وأحيانًا يكون من غير المناسب إعادة استخدامه في Python على العكس.
حسنا ، لماذا السرعة؟
حان الوقت لتذكر سبب بدء كل شيء.
ملاحظة: لقد نسيت Python بأمان ، لذا اكتب تحسيناتك في التعليقات ، وسأحاول تشغيلها على الكمبيوتر المحمول الخاص بي ومشاهدة وقت التشغيل.
إذن ، مثالان صغيران بعلامات قياس دقيقة.
شيء متجهالمشكلة: يتم تغذية المتجه X من 0 و 1 لإدخال الدالة. من الضروري تحويله إلى متجه 1 و (-1) (1 -> 1 ، 0 -> -1) وحساب عدد المعاملات من تحويل فورييه لهذا المتجه القيمة المطلقة تتجاوز الحدود.
def process_fft(x, boundary): return np.count_nonzero(np.abs(np.fft.fft(2*x-1)) > boundary) %timeit process_fft(x, 2000) 84.1 ms ± 335 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
function process_fft(x, boundary) count(t -> t > boundary, abs.(fft(2*x.-1))) end @benchmark process_fft(x, 2000) BenchmarkTools.Trial: memory estimate: 106.81 MiB allocs estimate: 61 -------------- minimum time: 80.233 ms (4.75% GC) median time: 80.746 ms (4.70% GC) mean time: 85.000 ms (8.67% GC) maximum time: 205.831 ms (52.27% GC) -------------- samples: 59 evals/sample: 1
لن نرى أي شيء يثير الدهشة هنا - على الرغم من كل ذلك ، فهم لا يعتبرونه بأنفسهم ، ولكن يمررونه إلى برامج فورتران المحسنة بشكل جيد.
شيء غير قابل للتوجيهيتم إدخال صفيف من 0 و 1 إلى الإدخال. أوجد طول أطول تتابع متتالي للوحدات المتتالية.
def longest(x): maxLen = 0 currLen = 0
function longest(x) maxLen = 0 currLen = 0 # This will count the number of ones in the block for bit in x if bit == 1 currLen += 1 maxLen = max(maxLen, currLen) else maxLen = max(maxLen, currLen) currLen = 0 end end return max(maxLen, currLen) end @benchmark longest(x) BenchmarkTools.Trial: memory estimate: 0 bytes allocs estimate: 0 -------------- minimum time: 9.094 ms (0.00% GC) median time: 9.248 ms (0.00% GC) mean time: 9.319 ms (0.00% GC) maximum time: 12.177 ms (0.00% GC) -------------- samples: 536 evals/sample: 1
الفرق واضح بالعين المجردة. نرحب بنصائح حول "إنهاء" و / أو توجيه الإصدار المتقطع. أود أيضًا أن أشير إلى أن البرامج متطابقة تقريبًا. على سبيل المثال ، لم أسجل نوعًا واحدًا في جوليا (مقارنة بالنوع السابق) - كل نفس ، كل شيء يعمل بسرعة.
أود أيضًا أن أشير إلى أن الإصدارات المقدمة لم يتم تضمينها في البرنامج النهائي ، ولكن تم تحسينها بشكل أكبر ؛ هنا يتم إعطاؤهم كمثال وبدون مضاعفات غير ضرورية (إعادة توجيه ذاكرة إضافية في جوليا للقيام rfft في المكان ، وما إلى ذلك).
ما خرج في النهاية؟
لن أعرض الكود النهائي لـ Python و Julia: هذا سر (على الأقل في الوقت الحالي). في الوقت الذي توقفت فيه لإنهاء نسخة الثعبان ، عملت على جميع اختبارات NIST على مجموعة من 16 ميغابايت من الأحرف الثنائية في ~ 50 ثانية. في جوليا في الوقت الحالي ، تعمل جميع الاختبارات على نفس الحجم ~ 20 ثانية. قد يكون أنني أحمق ، وكان هناك مجال للتحسينات ، إلخ. الخ. ولكن هذا أنا ، بكل ما أنا عليه ، بكل مزاياه وعيوبه ، وفي رأيي ، ليست السرعة الكروية للبرامج في فراغ في لغات البرمجة هي التي يجب مراعاتها ، ولكن كيف يمكنني شخصياً برمجتها (دون أخطاء فادحة حقًا).
لماذا كتبت كل هذا؟
أصبح الناس
هنا مهتمين ؛ قررت أن أكتب عندما يحين الوقت. أعتقد في المستقبل أن خوض جوليا بشكل أكثر شمولاً ، مع مثال على حل بعض المشاكل النموذجية. لماذا؟ المزيد من اللغات ، جيدة ومختلفة ، ودع كل من يريد أن يجد شيئًا يناسبه شخصيًا.