كانت هناك بالفعل مقالات عن Habré حول تفاصيل تنفيذ
مدير ذاكرة CPython ،
Pandas ، كتبت مقالًا حول
تنفيذ القاموس .
يبدو أنه يمكنك الكتابة عن نوع عدد صحيح منتظم؟ ومع ذلك ، ليس كل شيء بسيطًا جدًا ونوع الأعداد الصحيحة غير واضح.
إذا كنت تتساءل لماذا
x * 2 أسرع من x << 1 .
وكيف تحريك الخدعة التالية:
>>> 42 == 4 True >>> 42 4 >>> 1 + 41 4
ثم يجب عليك قراءة هذا المقال.
لا توجد أنواع بدائية في الثعبان - كل المتغيرات هي كائنات ويتم تخصيصها في الذاكرة الديناميكية. في الوقت نفسه ، يتم تمثيل الأعداد الصحيحة بنوع واحد فقط (لا نعتبر العلامة
العشرية ) - PyLongObject. تطبيق والإعلانات التي تكمن في ملفات longobject.h و longintrepr.h و longobject.c.
struct _longobject { PyObject_VAR_HEAD
يوجد في أي كائن في CPython حقلان: ob_refcnt - عداد الإشارات إلى الكائن و ob_type - مؤشر لنوع الكائن ؛ إلى الكائنات التي يمكن تغيير طولها ، يضاف حقل ob_size - الحجم المخصص الحالي (قد يكون المستخدم أقل).
وبالتالي ، يتم تمثيل نوع الأعداد الصحيحة بمجموعة من الطول المتغير من أرقام منفصلة ، لذلك فإن الثعبان خارج الصندوق يدعم الحساب الطويل ، في الإصدار الثاني من اللغة كان هناك نوع منفصل من الأعداد الصحيحة "العادية". تم إنشاء أعداد صحيحة "طويلة" باستخدام الحرف L أو إذا كانت نتيجة العمليات مع تسبب في تجاوز من قبل تلك العادية ؛ في الإصدار الثالث تقرر رفض ذلك.
قيمة عدد صحيح المخزنة بواسطة بنية _longobject تساوي:
يتم استخدام نوع 32 بت غير موقعة (uint32_t) على أنظمة 64 بت ونوع قصير غير موقّع 16 بت على 32 بت.
تفرض الخوارزميات المستخدمة في التنفيذ قيودًا صارمة على SHIFT من الصيغة السابقة ، على وجه الخصوص ، يجب تقسيمها على 15 ، لذلك يدعم cpython الآن قيمتين: 30 و 15 ، على التوالي ، للأنظمة 64 و 32 بت. بالنسبة للقيم السالبة ، يكون ob_size له قيمة سالبة ، ويتم إعطاء الصفر من خلال رقم الذي ob_size = 0.
عند إجراء العمليات الحسابية ، يقوم المترجم بالتحقق من الطول الحالي للمعاملات ، إذا كان كلاهما يتكون من بت واحد ، ثم يتم تنفيذ الحساب القياسي.
static PyObject * long_add(PyLongObject *a, PyLongObject *b) { ...
الضرب له بنية مماثلة ، بالإضافة إلى ذلك ، يقوم المترجم بتنفيذ
خوارزمية Karatsuba والتربيع السريع ، ومع ذلك ، لا يتم تنفيذها لكل "ضرب طويل" ، ولكن فقط لأعداد كبيرة بما فيه الكفاية ، وعدد الأرقام التي يتم تقديمها بواسطة ثوابتين:
static PyObject * long_mul(PyLongObject *a, PyLongObject *b) { ...
ومع ذلك ، لا تملك أوامر shift فحصًا لحالة الأعداد الصحيحة "القصيرة" ؛ يتم تنفيذها دائمًا في حالة الحساب الطويل. لذلك ، ضرب 2 أسرع من التحول. من المثير للاهتمام أن نلاحظ أن التقسيم أبطأ من التحول الصحيح ، والذي لا يتحقق أيضًا من حالة الأعداد الصحيحة المكونة من رقم واحد. لكن القسمة أكثر تعقيدًا: هناك طريقة واحدة تحسب أولاً الحاصل ، ثم الباقي ، يتم تمرير NULL إليها ، إذا كنت بحاجة إلى حساب شيء واحد ، وهو أن الطريقة يجب أن تتحقق من هذه الحالة أيضًا ، كل هذا يزيد من الحمل.
لا تشتمل وظيفة المقارنة أيضًا على حالة خاصة للأعداد الصحيحة "القصيرة".
static int long_compare(PyLongObject *a, PyLongObject *b) { Py_ssize_t sign;
صفائف (قوائم) الأرقام
عند إنشاء متغير نوع صحيح ، يجب على المترجم تخصيص مساحة كافية في الذاكرة الديناميكية ، ثم تعيين عداد المرجع (type ssize_t) ، وهو مؤشر لكتابة PyLongObject ، والحجم الحالي لصفيف البت (ssize_t) الحالي وتهيئة الصفيف نفسه. بالنسبة لأنظمة 64 بت ، يكون الحد الأدنى لحجم الهيكل: 2 * ssize_t + pointer + digit = 2 * 8 + 8 + 4 = 28 بايت. تظهر مشاكل إضافية عند إنشاء قوائم الأرقام: بما أن الأرقام ليست نوعًا بدائيًا ، والقوائم الموجودة في مخزن python تشير إلى الكائنات ، فإن الكائنات ليست متتابعة في الذاكرة الديناميكية.
يؤدي هذا الترتيب إلى إبطاء التكرار على المصفوفة: في الواقع ، يتم الوصول العشوائي ، مما يجعل من المستحيل التنبؤ بالانتقالات.
لتجنب التخصيص المفرط للذاكرة الديناميكية ، مما يبطئ وقت التشغيل ويساهم في تجزئة الذاكرة ، يتم تنفيذ التحسين في cpython: في مرحلة البدء ، يتم تخصيص مجموعة من الأعداد الصحيحة الصغيرة: من -5 إلى 256.
#ifndef NSMALLPOSINTS #define NSMALLPOSINTS 257
نتيجة لذلك ، يتم تمثيل قائمة الأرقام في cpython في الذاكرة كما يلي:

هناك فرصة للوصول إلى قائمة محددة مسبقًا من أعداد صحيحة صغيرة من البرنامج النصي ، مسلحة
بهذه المقالة ووحدة نمطية ctypes القياسية:
إخلاء المسؤولية: يتم توفير الكود التالي كما هو ، لا يتحمل المؤلف أية مسؤولية ولا يمكنه ضمان حالة المترجم الشفهي ، وكذلك الصحة العقلية لك ولزملائك ، بعد تشغيل هذا الرمز.
import ctypes
يتم تخزين Honest PyLongObject في هذه القائمة ، أي أنها تحتوي على عداد مرجعي ، على سبيل المثال ، يمكنك معرفة عدد الأصفار التي يستخدمها النص البرمجي واستخدام المترجم.
من التطبيق الداخلي للأعداد الصحيحة ، يترتب على ذلك أن الحساب في cpython لا يمكن أن يكون سريعًا ، حيث تتكرر اللغات الأخرى بشكل متتابع على المصفوفة ، وقراءة الأرقام في السجلات والاتصال مباشرة بالعديد من إرشادات المعالج ، يتم تمييز cpython في جميع أنحاء الذاكرة ، مع تنفيذ طرق معقدة إلى حد ما. في أبسط الحالات للأعداد الصحيحة ذات البت الواحد ، والتي ستكون كافية لاستدعاء تعليمة واحدة ، يجب أن يقوم المترجم بمقارنة الأحجام ، ثم إنشاء كائن في الذاكرة الديناميكية ، وملء حقول الخدمة ، وإرجاع مؤشر إليها ؛ علاوة على ذلك ، تتطلب هذه الإجراءات قفل GIL. أنت الآن تفهم سبب ظهور المكتبة الغريبة ولماذا تحظى بشعبية كبيرة.
أرغب في إنهاء المقالة المتعلقة بالنوع الكامل في cpython ، فجأة ، بمعلومات حول النوع Boolean.
الحقيقة هي أنه لفترة طويلة في الثعبان لم يكن هناك نوع منفصل للمتغيرات المنطقية ، فكل الوظائف المنطقية ترجع 0 و 1. ومع ذلك ،
فقد قرروا إدخال نوع جديد. في الوقت نفسه ، تم تنفيذه كنوع تابع إلى الكل.
PyTypeObject PyBool_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0)
علاوة على ذلك ، كل من قيم نوع Boolean هي حرف مفرد ، متغير Boolean هو مؤشر لمثيل True أو False (لا يتم تطبيق None بطريقة مماثلة).
struct _longobject _Py_FalseStruct = { PyVarObject_HEAD_INIT(&PyBool_Type, 0) { 0 } }; struct _longobject _Py_TrueStruct = { PyVarObject_HEAD_INIT(&PyBool_Type, 1) { 1 } }; static PyObject * bool_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *x = Py_False; long ok; if (!_PyArg_NoKeywords("bool", kwds)) return NULL; if (!PyArg_UnpackTuple(args, "bool", 0, 1, &x)) return NULL; ok = PyObject_IsTrue(x); if (ok < 0) return NULL; return PyBool_FromLong(ok); } PyObject *PyBool_FromLong(long ok) { PyObject *result; if (ok) result = Py_True; else result = Py_False; Py_INCREF(result); return result; }
PyObject_IsTrue (x) عبارة عن آلية صعبة لحساب قيمة منطقية ، يمكنك أن ترى ذلك
هنا في القسم الخاص بوظيفة bool أو في
الوثائق .
يؤدي هذا الإرث إلى بعض التأثيرات المضحكة ، مثل المساواة الحقيقية بين True و 1 ، وعدم القدرة على وجود True و 1 كمفاتيح في القاموس والمجموعة ، ومقبولية العمليات الحسابية على نوع Boolean:
>>> True == 1 True >>> {True: "true", 1: "one"} {True: 'one'} >>> True * 2 + False / (5 * True) - (True << 3) -6.0 >>> False < -1 False
لغة الثعبان رائعة بسبب مرونتها وقابليتها للقراءة ، ومع ذلك ، يجب أن يؤخذ في الاعتبار أن كل هذا يأتي بثمن ، على سبيل المثال ، في شكل تجريدات زائدة عن الحاجة ونفقات عامة ، والتي غالباً ما لا نفكر فيها أو نخمنها. آمل أن يكون هذا المقال قد سمح لك بتبديد "ضباب الحرب" بقليل على الكود المصدري للمترجم الشفوي ، وربما حتى يحثك على دراسته. من السهل جدًا قراءة رمز المترجم الشفهي ، تمامًا مثل الكود الموجود على بيثون نفسه ، وستتيح لك دراسته معرفة ليس فقط كيفية تنفيذ المترجم الشفهي ، ولكن أيضًا حلول الخوارزمية المثيرة للاهتمام وحلول النظام ، فضلاً عن إمكانية كتابة كود أكثر كفاءة أو أن تصبح مطور cpython نفسه.