PHP 8.0: Νέες συναρτήσεις, κλάσεις και JIT (4/4)

3 χρόνια πριν Από το David Grudl  

Κυκλοφόρησε η έκδοση 8.0 της PHP. Είναι γεμάτη από νέα χαρακτηριστικά όπως καμία άλλη έκδοση πριν. Η εισαγωγή τους άξιζε τέσσερα ξεχωριστά άρθρα. Στο τελευταίο μέρος θα ρίξουμε μια ματιά στις νέες συναρτήσεις και κλάσεις και θα παρουσιάσουμε τον μεταγλωττιστή Just in Time.

Νέες συναρτήσεις

Η τυπική βιβλιοθήκη της PHP διαθέτει εκατοντάδες συναρτήσεις και στην έκδοση 8.0 εμφανίστηκαν έξι νέες. Δεν φαίνονται πολλές, αλλά οι περισσότερες από αυτές διορθώνουν τα αδύνατα σημεία της γλώσσας. Κάτι που ταιριάζει όμορφα με την όλη ιδέα της έκδοσης 8.0, η οποία συσφίγγει και ενοποιεί την PHP όπως καμία άλλη έκδοση πριν. Επισκόπηση όλων των νέων συναρτήσεων και μεθόδων μπορείτε να βρείτε στον οδηγό μετάβασης.

str_contains() str_starts_with() str_ends_with()

Συναρτήσεις για να προσδιορίσετε αν μια συμβολοσειρά αρχίζει, τελειώνει ή περιέχει μια υποσυμβολοσειρά.

if (str_contains('Nette', 'te')) {
	...
}

Με την έλευση αυτής της τριάδας, η PHP ορίζει πώς να χειρίζεται μια κενή συμβολοσειρά κατά την αναζήτηση, κάτι που τηρούν όλες οι άλλες σχετικές συναρτήσεις, και που είναι ότι μια κενή συμβολοσειρά βρίσκεται παντού:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (previously false)

Χάρη σε αυτό, η συμπεριφορά της τριάδας είναι απολύτως πανομοιότυπη με τα ανάλογα της Nette:

str_contains()      # Nette\Utils\String::contains()
str_starts_with()   # Nette\Utils\String::startsWith()
str_ends_with()     # Nette\Utils\String::endsWith()

Γιατί είναι τόσο σημαντικές αυτές οι λειτουργίες; Οι τυποποιημένες βιβλιοθήκες όλων των γλωσσών επιβαρύνονται πάντα από την ιστορική εξέλιξη- οι ασυνέπειες και τα λάθη δεν μπορούν να αποφευχθούν. Ταυτόχρονα όμως είναι και μια μαρτυρία της εκάστοτε γλώσσας. Παραδόξως, από την 25χρονη PHP λείπουν συναρτήσεις για βασικές λειτουργίες όπως η επιστροφή του πρώτου ή του τελευταίου στοιχείου ενός πίνακα, η διαφυγή HTML χωρίς δυσάρεστες εκπλήξεις ( τοhtmlspecialchars δεν αποφεύγει την απόστροφο) ή απλά η αναζήτηση μιας συμβολοσειράς σε μια συμβολοσειρά. Δεν ισχύει ότι μπορεί να παρακαμφθεί με κάποιο τρόπο, γιατί το αποτέλεσμα δεν είναι ευανάγνωστος και κατανοητός κώδικας. Αυτό είναι ένα μάθημα για όλους τους συγγραφείς API. Όταν βλέπετε ότι μεγάλο μέρος της τεκμηρίωσης της συνάρτησης καταλαμβάνεται από επεξηγήσεις παγίδων (όπως οι τιμές επιστροφής της strpos), είναι ένα σαφές σημάδι για να τροποποιήσετε τη βιβλιοθήκη και να προσθέσετε την str_contains.

get_debug_type()

Αντικαθιστά το παρωχημένο πλέον get_type(). Αντί για long types όπως το integer, επιστρέφει το σήμερα χρησιμοποιούμενο int, στην περίπτωση των αντικειμένων επιστρέφει απευθείας τον τύπο:

Value gettype() get_debug_type()
'abc' string string
[1, 2] array array
231 integer int
3.14 double float
true boolean bool
null NULL null
new stdClass object stdClass
new Foo\Bar object Foo\Bar
function() {} object Closure
new class {} object class@anonymous
new class extends Foo {} object Foo@anonymous
curl_init() resource resource (curl)
curl_close($ch) resource (closed) resource (closed)

Μετανάστευση πόρων σε αντικείμενα

Οι τιμές των τύπων πόρων προέρχονται από μια εποχή που η PHP δεν είχε ακόμα αντικείμενα, αλλά τα χρειαζόταν πραγματικά. Έτσι γεννήθηκαν οι πόροι. Σήμερα έχουμε αντικείμενα και, σε σύγκριση με τους πόρους, λειτουργούν πολύ καλύτερα με τον garbage collector, οπότε το σχέδιο είναι να τα αντικαταστήσουμε σταδιακά όλα με αντικείμενα.

Από την PHP 8.0, οι πόροι images, curl joins, openssl, xml, κλπ. αλλάζουν σε αντικείμενα. Στην PHP 8.1, θα ακολουθήσουν οι συνδέσεις FTP κ.λπ.

$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage  // true
is_resource($res)        // false - BC break

Αυτά τα αντικείμενα δεν έχουν ακόμα μεθόδους, ούτε μπορείτε να τα ενσαρκώσετε άμεσα. Μέχρι στιγμής, είναι πραγματικά μόνο ένα θέμα απαλλαγής από παρωχημένους πόρους της PHP χωρίς να αλλάξει το API. Και αυτό είναι καλό, επειδή η δημιουργία ενός καλού API είναι ένα ξεχωριστό και δύσκολο έργο. Κανείς δεν επιθυμεί τη δημιουργία νέων κλάσεων PHP όπως η SplFileObject με μεθόδους που ονομάζονται fgetc() ή fgets().

PhpToken

Ο tokenizer και οι συναρτήσεις γύρω από το token_get_all μεταφέρονται επίσης σε αντικείμενα. Αυτή τη φορά δεν πρόκειται να απαλλαγούμε από τους πόρους, αλλά παίρνουμε ένα ολοκληρωμένο αντικείμενο που αντιπροσωπεύει ένα PHP token.

<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // instance PhpToken

echo $token->id;             // T_OPEN_TAG
echo $token->text;           // '<?php'
echo $token->line;           // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING);   // false
echo $token->isIgnorable();  // true

Η μέθοδος isIgnorable() επιστρέφει true για τα tokens T_WHITESPACE, T_COMMENT, T_DOC_COMMENT και T_OPEN_TAG.

Αδύναμοι χάρτες

Οι αδύναμοι χάρτες σχετίζονται με τον garbage collector, ο οποίος απελευθερώνει από τη μνήμη όλα τα αντικείμενα και τις τιμές που δεν χρησιμοποιούνται πλέον (δηλαδή δεν υπάρχει μεταβλητή ή ιδιότητα που να τα περιέχει). Επειδή τα νήματα της PHP είναι βραχύβια και έχουμε άφθονη μνήμη διαθέσιμη στους διακομιστές μας, συνήθως δεν ασχολούμαστε καθόλου με θέματα που αφορούν την αποτελεσματική απελευθέρωση μνήμης. Αλλά για σενάρια με μεγαλύτερη διάρκεια εκτέλεσης, είναι απαραίτητα.

Το αντικείμενο WeakMap είναι παρόμοιο με το SplObjectStorage Και τα δύο χρησιμοποιούν αντικείμενα ως κλειδιά και επιτρέπουν την αποθήκευση αυθαίρετων τιμών κάτω από αυτά. Η διαφορά είναι ότι το WeakMap δεν εμποδίζει την απελευθέρωση του αντικειμένου από τον garbage collector. Δηλαδή, αν το μόνο μέρος, όπου το αντικείμενο υπάρχει αυτή τη στιγμή, είναι ένα κλειδί στον αδύναμο χάρτη, θα αφαιρεθεί από τον χάρτη και τη μνήμη.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'data for $obj';

dump(count($map));  // 1
unset($obj);
dump(count($map));  // 0

Σε τι χρησιμεύει; Για παράδειγμα, για προσωρινή αποθήκευση. Ας έχουμε μια μέθοδο loadComments() στην οποία περνάμε ένα άρθρο ιστολογίου και επιστρέφει όλα τα σχόλιά του. Δεδομένου ότι η μέθοδος καλείται επανειλημμένα για το ίδιο άρθρο, θα δημιουργήσουμε μια άλλη getComments(), η οποία θα αποθηκεύει στην κρυφή μνήμη το αποτέλεσμα της πρώτης μεθόδου:

class Comments
{
	private WeakMap $cache;

	public function __construct()
	{
		$this->cache = new WeakMap;
	}

	public function getComments(Article $article): ?array
	{
		$this->cache[$article] ??= $this->loadComments($article);
		return $this->cache[$article]
	}

	...
}

Το θέμα είναι ότι όταν το αντικείμενο $article απελευθερώνεται (για παράδειγμα, η εφαρμογή αρχίζει να εργάζεται με ένα άλλο άρθρο), η καταχώρηση του απελευθερώνεται επίσης από την κρυφή μνήμη.

PHP JIT (Just in Time Compiler)

Ίσως γνωρίζετε ότι η PHP μεταγλωττίζεται σε λεγόμενο opcode, δηλαδή σε εντολές χαμηλού επιπέδου που μπορείτε να δείτε εδώ, για παράδειγμα και οι οποίες εκτελούνται από μια εικονική μηχανή PHP. Και τι είναι ένας JIT; Ο JIT μπορεί να μεταγλωττίσει με διαφάνεια την PHP απευθείας σε κώδικα μηχανής, ο οποίος εκτελείται απευθείας από τον επεξεργαστή, έτσι ώστε να παρακάμπτεται η πιο αργή εκτέλεση από την εικονική μηχανή.

Η JIT έχει ως εκ τούτου ως στόχο την επιτάχυνση της PHP.

Η προσπάθεια για την εφαρμογή του JIT στην PHP χρονολογείται από το 2011 και υποστηρίζεται από τον Dmitry Stogov. Από τότε, έχει δοκιμάσει 3 διαφορετικές υλοποιήσεις, αλλά καμία από αυτές δεν μπήκε σε τελική έκδοση της PHP για τρεις λόγους: το αποτέλεσμα δεν ήταν ποτέ σημαντική αύξηση της απόδοσης για τυπικές εφαρμογές ιστού- περιπλέκει τη συντήρηση της PHP (δηλαδή κανείς εκτός από τον Dmitry δεν την καταλαβαίνει 😉 )- υπήρχαν άλλοι τρόποι βελτίωσης της απόδοσης χωρίς να χρειάζεται να χρησιμοποιηθεί JIT.

Η αλματώδης αύξηση των επιδόσεων που παρατηρήθηκε στην έκδοση 7 της PHP ήταν ένα υποπροϊόν της εργασίας για το JIT, αν και παραδόξως δεν αναπτύχθηκε. Αυτό συμβαίνει μόνο τώρα στην PHP 8. Αλλά θα συγκρατήσω τις υπερβολικές προσδοκίες: μάλλον δεν θα δείτε καμία επιτάχυνση.

Γιατί λοιπόν η JIT μπαίνει στην PHP; Πρώτον, οι άλλοι τρόποι βελτίωσης της απόδοσης εξαντλούνται σιγά σιγά, και η JIT είναι απλά το επόμενο βήμα. Στις κοινές εφαρμογές ιστού, δεν φέρνει καμία βελτίωση της ταχύτητας, αλλά επιταχύνει σημαντικά, για παράδειγμα, τους μαθηματικούς υπολογισμούς. Αυτό ανοίγει τη δυνατότητα να αρχίσετε να γράφετε αυτά τα πράγματα σε PHP. Στην πραγματικότητα, θα ήταν δυνατό να υλοποιηθούν ορισμένες συναρτήσεις απευθείας στην PHP που προηγουμένως απαιτούσαν άμεση υλοποίηση σε C λόγω ταχύτητας.

Η JIT αποτελεί μέρος της επέκτασης opcache και ενεργοποιείται μαζί με αυτήν στο php.ini (διαβάστε την τεκμηρίωση για αυτά τα τέσσερα ψηφία):

zend_extension=php_opcache.dll
opcache.jit=1205              ; configuration using four digits OTRC
opcache.enable_cli=1          ; in order to work in the CLI as well
opcache.jit_buffer_size=128M  ; dedicated memory for compiled code

Μπορείτε να επαληθεύσετε ότι το JIT εκτελείται, για παράδειγμα, στον πίνακα πληροφοριών του Tracy Bar.

Το JIT λειτουργεί πολύ καλά αν όλες οι μεταβλητές έχουν σαφώς καθορισμένους τύπους και δεν μπορούν να αλλάξουν ακόμα και όταν καλείτε τον ίδιο κώδικα επανειλημμένα. Αναρωτιέμαι, λοιπόν, αν κάποια μέρα θα δηλώνουμε τύπους και στην PHP για τις μεταβλητές: string $s = 'Bye, this is the end of the series';

Τελευταίες θέσεις