آموزش PHP جلسه سوم: معرفی PHP 7 و کامپایلر Just-In-Time

شاید برای شما هم سؤال پیش بیاید چرا بجای نسخه ی 6، نسخه ی 7 منتشر شد!؟
پس از یک رأی گیری در سال 2014، رسما تصمیم گرفته شد، نام نسخه ی بعدی را PHP 7 بگذارند.
یکی از دلایلی که مطرح است، تلاش ناموفق در انتشار نسخه ی PHP 6 و وجود کتاب ها و منابع مختلفی که حتی قبل از انتشار PHP 6 نوشته شده بود که باعث رجوع و سردرگمی برنامه نویسان این حوزه میشد.

PHP 7 با هدف، بهبود بخشیدن به Performance برنامه ها (حداقل مشابه چیزی که فیسبوک در HHVM فراهم کرده) به توسعه ی خود ادامه پیدا کرد تا بالاخره در سال 2015، این انتشار صورت گرفت.
PHP 7 با بهبود بخشیدن به کارایی برنامه ها و مصرف بهینه از منابع سرور، میتوان گفت به اهداف خودش رسیده. میتوانید گوشه ای از مقایسه هارو اینجا ببینید.

PHP 7، میتواند سریعتر هم باشد:
Dmitry Stogov گفته بود، توسعه ی PHPNG با انگیزه ی تحقیق در پیاده سازی موتور کامپایل Just In Time به اختصار JIT آغاز شد. اما با انتشار PHP 7 این موتور هنوز به نسخه ی رسمی PHP اضافه نشده و هنوز مراحل تست رو طی میکنه. البته در این شاخه، قابل دسترس هست که میتونید ببینید.

اینجا در مورد JIT صحبت کردیم که شاید واستون سؤال پیش بیاد که چی هست یا مگه PHP تفسیری نیست، پس کامپایل چیه و ...
برای شفاف شدن موضوع شاید بد نباشه در مورد نوع اجرا و تفسیر شدن کد های PHP صحبت کنیم.

تفسیر کدهای PHP از 3 فاز + فاز اجرای دستورات تشکیل شده:
مرحله اول Lexical Analyze: تمام نوشته های داخل سورس فایل، با توجه به فاصله هایی(WhiteSpace) که بینشان هست، شکسته میشن و این تکه ها در دسته بندی خاصی قرار میگیرند.

هر کدام از این تکه ها را یک Token میگوییم. مثلا <?php echo 'bithub.ir' ?> توکن های این دستور: <?php و echo و 'bithub.ir' و ?> و البته بهمراه 3 تا WhiteSpace.

حالا کد زیرو در نظر بگیرید:

<?php
$a = 5;
$b = 3;
echo $a + ($b * 2);

نتیجه ی آنالیز کد بالا به شکل زیر است:

Array
[
    [0] => Array
        [
            [0] => 379 /**T_OPEN_TAG**/
            [1] => <?php
            [2] => 1 /**Line Number**/
        ]

    [1] => Array
        [
            [0] => 320 /**T_VARIABLE**/
            [1] => $a
            [2] => 2
        ]

    [2] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 2
        ]

    [3] => =
    [4] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 2
        ]

    [5] => Array
        [
            [0] => 317 /**T_LNUMBER**/
            [1] => 5
            [2] => 2
        ]

    [6] => ;
    [7] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] => 
            [2] => 2
        ]

    [8] => Array
        [
            [0] => 320 /**T_VARIABLE**/
            [1] => $b
            [2] => 3
        ]

    [9] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 3
        ]

    [10] => =
    [11] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 3
        ]

    [12] => Array
        [
            [0] => 317 /**T_LNUMBER**/
            [1] => 3
            [2] => 3
        ]

    [13] => ;
    [14] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] => 
            [2] => 3
        ]

    [15] => Array
        [
            [0] => 328 /**T_ECHO**/
            [1] => echo
            [2] => 4
        ]

    [16] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 4
        ]

    [17] => Array
        [
            [0] => 320 /**T_VARIABLE**/
            [1] => $a
            [2] => 4
        ]

    [18] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 4
        ]

    [19] => +
    [20] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 4
        ]

    [21] => (
    [22] => Array
        [
            [0] => 320 /**T_VARIABLE**/
            [1] => $b
            [2] => 4
        ]

    [23] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 4
        ]

    [24] => *
    [25] => Array
        [
            [0] => 382 /**T_WHITESPACE**/
            [1] =>  
            [2] => 4
        ]

    [26] => Array
        [
            [0] => 317 /**T_LNUMBER**/
            [1] => 2
            [2] => 4
        ]

    [27] => )
    [28] => ;
]

همونطور که میبینید، سورس کد صفحه بخش بخش شده و در هر قسمت به ترتیب آیدی توکن و توکن و شماره لاین مشخص شده است.

T_OPEN_TAG و بقیه ی عبارت هایی که در مثال بالا میبینید، ثابت هایی هستند که PHP برای Token ID ها در نظر گرفته است. میتونید این لیست رو ببینید.

PHP این امکان رو برای برنامه نویسان فراهم کرده تا بتوانند در برنامه های خودشان(مثل Syntax Highlighter و ...) یک سیستم آنالیزی پیاده کنند. اطلاعات بیشتر در این لینک.

بعد از بررسی و آنالیز، اگر در این مرحله خطایی وجود نداشت وارد فاز دوم میشه. 
مرحله دوم Syntax Analyze یا Parsing: توکن ها رو دریافت کرده و تا جای ممکن عملیات ساده سازی(مانند حذف فضاهای خالی و قسمت های ناکارامد) انجام میگیره و در نهایت، درخت چکیده(Abstract Syntax Tree به اختصار AST) تولید میشه.

Abstract Syntax Tree در PHP 7 اضافه شده است.

درخت چکیده ی مثال بالا رو در زیر ببینید:

Assign statement
  |-- Variable $a
  `-- Integer, value 5
Assign statement
  |-- Variable $b
  `-- Integer, value 3

Echo statement
  `-- Add operation
    |-- Left
    | `-- Variable $a
    `-- Right
      `-- Multiply operation
        |-- Left
        | `-- Variable $b
        `-- Right
          `-- Integer, value 2

مرحله سوم OpCode: بعد از چک کردن قوانین گرامری و تشخیص مناسب بودن عبارات وارد شده، حالا در این بخش AST تولید شده از مرحله ی قبل، کامپایل(ترجمه) میشه به آرایه ای از Operation Code ها یا به اختصار OpCode ها.

OpCode ها دستورات سطح پایینی هستن که برای ماشین Zend قابل فهم هستن ازیرو بهشون کد میانی هم گفته میشه.

مرحله چهارم Execute: در نهایت Opcode ها توسط مفسر یکی یکی و به ترتیب دریافت شده و برای هر Opcode دستورالعمل آن در کد ماشین بصورت مستقیم توسط موتور Zend صدا زده میشه تا دستورات یکی یکی اجرا شوند.

بنابراین برخلاف کامپایلرها، مفسر دیگه کد میانی(OpCode) رو تبدیل به کد ماشین نمیکنه که بعد کد ماشین روی CPU اجرا بشه. مطابق همان دستوری که کد ماشین اجرا میکنه، مفسر بصورت مستقیم اون دستور رو صدا میزنه و اون دستور هم روی CPU اجرا میشه.

ازونجایی که تمام این مراحل روی رم سرور انجام میشود، در هربار فراخوانی صفحه مورد نظر، تمام این مراحل از سر گرفته میشود بنابرایندر اسکریپت های سنگین میتواند بار زیادی روی منابع سرور ایجاد کند.
برای حل این موضوع میتوان Opcode Cache به اختصار OpCache را در تنظیمات PHP فعال کرد. این کار باعث میشه، در مرحله ی اول که کد اجرا میشه، OpCode ها در رم سرور Cache بشن و دیگه تا زمانی که کد صفحه تغییری نکرده، در هر بار فراخوانی صفحه دیگه مراحل تولید OpCode یعنی اون 3 مرحله ی اول، تکرار نمیشه و مستقیم از روی رم OpCode ها رو خونده و در نهایت Execute میکند.
نتیجه ی این کار، صرفه جوبی در مصرف منابع سرور است.

همچنین میتوانید تصویر زیرو هم نگاهی بندازید:

 

اما کامپایلر JIT:

کار کامپایلر JIT تبدیل OpCode ها به کدهای ماشین هست. JIT هر موقع که نیاز باشد کامپایل را انجام میدهد و قرار نیست در هر بار اجرا این عمل تکرار شود. این تبدیل ممکنه در اجرای اول برنامه، زمان بیشتری نسبت به مفسر، مصرف کنه اما در فراخوانی های بعدی بخاطر کش شدن کدهای ماشین، این کدها با سرعت بیشتری اجرا میشوند.

در این قسمت میخوام مقایسه ای انجام بدم تا مقدار حافظه ی مصرفی رو در اجرای یک اسکریپت ببینیم. این تست روی ویندوز انجام شده است.
فرض کنید این اسکریپت رو میخوایم اجرا بگیریم:

$a = [];
for($i=0; $i < 100000; $i++)
{
    $a[] = ['abc','def','ghi','jkl','mno','pqr'];
}

echo memory_get_usage(true);

تابع memory_get_usage، مقدار مصرف حافظه را برحسب بایت برمیگرداند. برای تبدیل Byte به KB یا MB میتونید از تابع زیر استفاده کنید:

function convert($size)
{
    $unit = array('b','kb','mb','gb','tb','pb');
    return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i];
}

نتایج بدست آمده:
نسخه PHP 5.6 و OpCache غیر فعال: 120 MB
نسخه PHP 7 و OpCache غیر فعال44 MB
نسخه PHP 7 و OpCache فعال8 MB

از نتایج بدست آمده میتوانید تفاوت Performance نسخه های PHP 5.6 و PHP 7 را بهمراه OpCache در حالت فعال و غیر فعال، ببینید.

مطالب سایت را بدون ذکر منبع (http://bithub.ir) در جایی منتشر نکنید. با تیشکر.

مطالب مرتبط

برای ارسال نظر، باید عضو سایت شوید.