Python transpiler chain → 11l → C ++ [لتسريع كود Python والمزيد]




تتناول هذه المقالة التحويلات الأكثر إثارة للاهتمام التي تقوم بها سلسلة من اثنين من الناسخين (الأول يترجم رمز Python إلى رمز في لغة البرمجة الجديدة 11 لتر ، والثاني يترجم الرمز إلى 11 لتر في C ++) ، ويقارن أيضًا الأداء مع أدوات التسريع الأخرى / تنفيذ كود Python (PyPy و Cython و Nuitka).

استبدال الشرائح \ الشرائح بنطاقات

بيثون11 لتر
s[-1] s[-2] s[:-1] s[1:] s[:2] s[1:2] s[::2] s[1::2] s[3:10:2] 
 s.last s[(len)-2] s[0..<(len)-1] s[1..] s[0..<2] s[1..<2] s[(0..).step(2)] s[(1..).step(2)] s[(3..<10).step(2)] 
الإشارة الصريحة للفهرسة من نهاية الصفيف s[(len)-2] بدلاً من s[-2] مطلوبة لإزالة الأخطاء التالية:
  1. عندما يكون ، على سبيل المثال ، مطلوبًا للحصول على الحرف السابق بواسطة s[i-1] ، ولكن بالنسبة لـ i = 0 مثل هذا / هذا السجل بدلاً من خطأ سيعيد بصمت الحرف الأخير من السلسلة [ وعمليًا واجهت مثل هذا الخطأ - الالتزام ] .
  2. i = s.find(":") التعبير s[i:] بعد i = s.find(":") بشكل غير صحيح عندما لا يتم العثور على الحرف في السلسلة [ بدلاً من '' جزء من السلسلة بدءًا من الحرف الأول : ثم '' سيتم أخذ الحرف الأخير من السلسلة ] (وعمومًا ، أعتقد أن إرجاع -1 مع الدالة find() في Python غير صحيح أيضًا [ يجب إرجاع null / None [ وإذا كان -1 مطلوبًا ، فيجب كتابته بشكل صريح: i = s.find(":") ?? -1 ] ] )
  3. لن تعمل كتابة s[-n:] للحصول على آخر حرف n من السلسلة بشكل صحيح عندما تكون n = 0.

سلاسل عوامل المقارنة


للوهلة الأولى ، إنها ميزة بارزة في لغة Python ، ولكن من الناحية العملية يمكن التخلي عنها / الاستغناء عنها بسهولة باستخدام عامل التشغيل والنطاقات:
a < b < cb in a<..<c
a <= b < cb in a..<c
a < b <= cb in a<..c
0 <= b <= 9b in 0..9

فهم القائمة


وبالمثل ، كما اتضح ، يمكنك رفض ميزة أخرى مثيرة للاهتمام لفهم قائمة Python.
في حين أن بعض فهم القائمة يمجد ويفترض حتى التخلي عن `filter ()` و` map () ` ، وجدت أن:
  1. في جميع الأماكن التي رأيت فيها فهمًا لقائمة Python ، يمكنك بسهولة الحصول عليها باستخدام الوظائف "filter ()` و "map ()`.
     dirs[:] = [d for d in dirs if d[0] != '.' and d != exclude_dir] dirs[:] = filter(lambda d: d[0] != '.' and d != exclude_dir, dirs) '[' + ', '.join(python_types_to_11l[ty] for ty in self.type_args) + ']' '[' + ', '.join(map(lambda ty: python_types_to_11l[ty], self.type_args)) + ']' # Nested list comprehension: matrix = [ [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], ] [[row[i] for row in matrix] for i in range(4)] list(map(lambda i: list(map(lambda row: row[i], matrix)), range(4))) 
  2. `filter ()` و `map ()` في 11l تبدو أجمل من Python
     dirs[:] = filter(lambda d: d[0] != '.' and d != exclude_dir, dirs) dirs = dirs.filter(d -> d[0] != '.' & d != @exclude_dir) '[' + ', '.join(map(lambda ty: python_types_to_11l[ty], self.type_args)) + ']' '['(.type_args.map(ty -> :python_types_to_11l[ty]).join(', '))']' outfile.write("\n".join(x[1] for x in fileslist if x[0])) outfile.write("\n".join(map(lambda x: x[1], filter(lambda x: x[0], fileslist)))) outfile.write(fileslist.filter(x -> x[0]).map(x -> x[1]).join("\n")) 
    وبالتالي ، فإن الحاجة إلى فهم القائمة في 11 لتر تختفي فعليًا [يتم استبدال استبدال القائمة مع filter() و / أو map() أثناء تحويل رمز Python إلى 11 لتر تلقائيًا ] .

قم بتحويل سلسلة if-elif-else للتبديل


على الرغم من أن Python لا يحتوي على بيان تبديل ، إلا أنه أحد أجمل التركيبات في 11 لتر ، ولذا قررت إدراج التبديل تلقائيًا:
بيثون11 لتر
 ch = instr[i] if ch == "[": nesting_level += 1 elif ch == "]": nesting_level -= 1 if nesting_level == 0: break elif ch == "'": ending_tags.append(''') # '' elif ch == "'": assert(ending_tags.pop() == ''') 
 switch instr[i] '[' nesting_level++ ']' if --nesting_level == 0 loop.break "'" ending_tags.append("'") // '' "'" assert(ending_tags.pop() == "'") 
للاستكمال ، إليك رمز C ++ الذي تم إنشاؤه
 switch (instr[i]) { case u'[': nesting_level++; break; case u']': if (--nesting_level == 0) goto break_; break; case u''': ending_tags.append(u"'"_S); break; // '' case u''': assert(ending_tags.pop() == u'''); break; } 


تحويل القواميس الصغيرة إلى كود أصلي


ضع في اعتبارك هذا السطر من كود Python:
 tag = {'*':'b', '_':'u', '-':'s', '~':'i'}[prev_char()] 
على الأرجح ، هذا النوع من التسجيل ليس فعالًا جدًا [ من حيث الأداء ] ، ولكنه ملائم للغاية.

في 11 لتر ، الإدخال المقابل لهذا الخط [ والذي تم الحصول عليه بواسطة ناقل Python → 11l ] ليس مناسبًا فقط [ ومع ذلك ، ليس أنيقًا كما هو الحال في Python ] ، ولكنه سريع أيضًا:
 var tag = switch prev_char() {'*' {'b'}; '_' {'u'}; '-' {'s'}; '~' {'i'}} 

يتم ترجمة السطر أعلاه إلى:
 auto tag = [&](const auto &a){return a == u'*' ? u'b'_C : a == u'_' ? u'u'_C : a == u'-' ? u's'_C : a == u'~' ? u'i'_C : throw KeyError(a);}(prev_char()); 
[ سيتم تجميع استدعاء دالة لامدا بواسطة المترجم C ++ \ المضمنة أثناء عملية التحسين وستبقى فقط سلسلة المشغلين ?/: ]

في حالة تعيين متغير ، يتم ترك القاموس كما يلي:
بيثون
 rn = {'I': 1, 'V': 5, 'X': 10, 'L': 50, ...} 
11 لتر
 var rn = ['I' = 1, 'V' = 5, 'X' = 10, 'L' = 50, ...] 
C ++
 auto rn = create_dict(dict_of(u'I'_C, 1)(u'V'_C, 5)(u'X'_C, 10)(u'L'_C, 50)...); 

التقاط \ Ca التقاط المتغيرات الخارجية


في Python ، للإشارة إلى أن المتغير ليس محليًا ، ولكن يجب أخذه خارج [ الوظيفة الحالية ] ، يتم استخدام الكلمة الأساسية غير المحلية [ وإلا ، على سبيل المثال ، found = True سيتم التعامل معها على أنها إنشاء متغير محلي جديد found ، بدلاً من تعيين قيمة بالفعل المتغير الخارجي الموجود ] .
في 11l ، يتم استخدام البادئة @ لهذا:
بيثون11 لتر
 writepos = 0 def write_to_pos(pos, npos): nonlocal writepos outfile.write(...) writepos = npos 
 var writepos = 0 fn write_to_pos(pos, npos) @outfile.write(...) @writepos = npos 
C ++:
 auto writepos = 0; auto write_to_pos = [..., &outfile, &writepos](const auto &pos, const auto &npos) { outfile.write(...); writepos = npos; }; 

المتغيرات العالمية


على غرار المتغيرات الخارجية ، إذا نسيت الإعلان عن متغير عام في Python [ باستخدام الكلمة الرئيسية العامة ] ، فستحصل على خطأ غير مرئي:
 break_label_index = -1 ... def parse(tokens, source_): global source, tokeni, token, scope source = source_ tokeni = -1 token = None break_label_index = -1 scope = Scope(None) ... 
 var break_label_index = -1 ... fn parse(tokens, source_) :source = source_ :tokeni = -1 :token = null break_label_index = -1 :scope = Scope(null) ... 
كود 11l [ يمين ] ، على عكس Python [ يسار ] ، break_label_index خطأ 'متغير break_label_index ' غير معلن في break_label_index الترجمة.

فهرس / رقم بند الحاوية الحالي


أستمر في نسيان ترتيب المتغيرات التي ترجعها دالة Python التعددية {تأتي القيمة أولاً ، ثم الفهرس ، أو العكس بالعكس}. السلوك التناظري في Ruby - each.with_index - أسهل بكثير للتذكر: مع الفهرس يعني أن الفهرس يأتي بعد القيمة وليس قبلها. ولكن في 11l ، يسهل تذكر المنطق:
بيثون11 لتر
 items = ['A', 'B', 'C'] for index, item in enumerate(items): print(str(index) + ' = ' + item) 
 var items = ['A', 'B', 'C'] loop(item) items print(loop.index' = 'item) 

الأداء


يتم استخدام برنامج تحويل ترميز الكمبيوتر إلى HTML كبرنامج اختبار ، ويتم استخدام التعليمات البرمجية المصدر للمقالة حول ترميز الكمبيوتر الشخصي كبيانات المصدر [ نظرًا لأن هذه المقالة هي حاليًا أكبر المقالات المكتوبة على ترميز الكمبيوتر الشخصي ] ، ويتم تكرارها 10 مرات ، أي تم الحصول عليها من 48.8 كيلوبايت حجم ملف المقالة 488 كيلوبايت.

في ما يلي مخطط يوضح عدد المرات التي تكون فيها الطريقة المقابلة لتنفيذ كود Python أسرع من التنفيذ الأصلي [ CPython ] :

والآن أضف إلى الرسم البياني التنفيذ الذي تم إنشاؤه بواسطة Transpiler Python → 11l → C ++:

كان وقت التشغيل [ وقت تحويل ملف 488 كيلوبايت ] 868 مللي ثانية لـ CPython و 38 مللي ثانية لرمز C ++ الذي تم إنشاؤه [ يتضمن هذا الوقت كامل [ أي ليس فقط العمل مع البيانات في ذاكرة الوصول العشوائي ] تشغيل البرنامج بواسطة نظام التشغيل وجميع المدخلات / الإخراج [ قراءة الملف المصدر [ .pq ] وحفظ الملف الجديد [ .html ] إلى القرص ] ] .

أردت أيضًا تجربة Shed Skin ، لكنها لا تدعم الوظائف المحلية.
لا يمكن استخدام Numba أيضًا (يظهر خطأ "استخدام رمز تشغيل غير معروف LOAD_BUILD_CLASS").
هذا هو الأرشيف مع البرنامج المستخدم لمقارنة الأداء [ ضمن Windows ] (يتطلب Python 3.6 أو أعلى وحزم Python التالية: pywin32، cython).

المصدر في Python وإخراج Transpilers -> 11l و 11 l -> C ++:
بيثونولدت 11 لتر
(مع الكلمات الأساسية بدلاً من الحروف)
11 لتر
(بأحرف)
تم إنشاء C ++

Source: https://habr.com/ru/post/ar431318/


All Articles