PHP7新特性整理介绍篇

Java 发表评论

概述

PHP7将在2015年10月正式发布,PHP7 ,将会是PHP脚本语言的重大版本更新,同时将带来大幅的性能改进和新的特性,以及改进一些过时功能。 该 发布版本将会专注在性能加强,源自PHP版本树中的phpng分支。在硅谷公司的ZendCon会议,PHP工具厂商Zend技术官方讨论phpng和 PHP7的进度。“(本次升级)真正专注于帮助业界的应用程序显著加强执行速度,再加上,我们在PHP中的其他改进,”Zend的首席执行官安迪特曼斯 (曾参与了PHP语言的持续开发和发展)表示。

新特性

PHP7引擎( What will be in PHP 7 / PHPNG )

  • Performance Improvements with the addition of PHPNG engine.(使用PHPNG引擎来提升性能)

  • PHPNG不再使用zval的二级指针。大多数出现的zval**变量和参数都将改变成zval*。相应的,使用在这些变量上的宏Z_PP()也需要变成Z_P()。

  • More -> https://wiki.php.net/phpng-upgrading

  • JIT – Just in Time compiler

  • 即时编译 (参考百度百科介绍)

  • Just In Time(即时编译)是一种软件优化技术,指在运行时才会去编译字节码为机器码。从直觉出发,我们都很容易认为,机器码是计算机能够直接识别和执行的,比起Zend读取opcode逐条执行效率会更高。其中,HHVM(HipHop Virtual Machine,HHVM是一个Facebook开源的PHP虚拟机)就采用JIT,让他们的PHP性能测试提升了一个数量级,放出一个令人震惊的测试结果,也让我们直观地认为JIT是一项点石成金的强大技术。

  • 通过JIT,可以降低VM的开销,同时,通过指令优化,可以间接降低内存管理的开发,因为可以减少内存分配的次数。然而,对于真实的WordPress项目来说,CPU耗时只有25%在VM上,主要的问题和瓶颈实际上并不在VM上。因此,JIT的优化计划,最后没有被列入该版本的PHP7特性中。不过,它很可能会在更后面的版本中实现,这点也非常值得我们期待哈。

  • Abstract Syntax Tree for compilation(抽象语法树编译)

  • Asynchronous refactoring of the I/O layer. 对I/O层的异步重构

  • Multi-threaded build in Web Server多线程构建Web服务器

  • Expanded use of ->, [], (), {}, and :: operators 扩展使用 ->, [], (), {}, 和 :: 符号

  • 100% increase in performance性能提升 100% (应该是QPS)

  • Cool Name: PHPNG 酷名:PHPNG引擎

Zval的改变

PHP的各种类型的变量,其实,真正存储的载体就是Zval,它特点是海纳百川,有容乃大。从本质上看,它是C语言实现的一个结构体(struct)。对于写PHP的同学,可以将它粗略理解为是一个类似array数组的东西。

PHP5的Zval,内存占据24个字节,PHP7的Zval,内存占据16个字节。
Zval从24个字节下降到16个字节,为什么会下降呢,这里需要补一点点的C语言基础,辅助不熟悉C的同学理解。struct和union(联合体)有点不同,Struct的每一个成员变量要各自占据一块独立的内存空间,而union里的成员变量是共用一块内存空间(也就是说修改其中一个成员变量,公有空间就被修改了,其他成员变量的记录也就没有了)。因此,虽然成员变量看起来多了不少,但是实际占据的内存空间却下降了。

不需要引用的类型:NULL、Boolean、Long、Double
需要引用的类型:String、Array、Object、Resource、Reference

内部类型zend_string

Zend_string是实际存储字符串的结构体,实际的内容会存储在val(char,字符型)中,而val是一个char数组,长度为1(方便成员变量占位)。

PHP数组的变化(HashTable和Zend Array)

在编写PHP程序过程中,使用最频繁的类型莫过于数组,PHP5的数组采用HashTable实现。如果用比较粗略的概括方式来说,它算是一个支持双向链表的HashTable,不仅支持通过数组的key来做hash映射访问元素,也能通过foreach以访问双向链表的方式遍历数组元素。

当我们通过key值访问一个元素内容的时候,有时需要3次的指针跳跃才能找对需要的内容。而最重要的一点,就在于这些数组元素存储,都是分散在各个不同的内存区域的。同理可得,在CPU读取的时候,因为它们就很可能不在同一级缓存中,会导致CPU不得不到下级缓存甚至内存区域查找,也就是引起CPU缓存命中下降,进而增加更多的耗时。

新版本的数组结构,非常简洁,让人眼前一亮。最大的特点是,整块的数组元素和hash映射表全部连接在一起,被分配在同一块内存内。如果是遍历一个整型的简单类型数组,效率会非常快,因为,数组元素(Bucket)本身是连续分配在同一块内存里,并且,数组元素的zval会把整型元素存储在内部,也不再有指针外链,全部数据都存储在当前内存区域内。当然,最重要的是,它能够避免CPU Cache Miss(CPU缓存命中率下降)。

Zend Array的变化:

(1) 数组的value默认为zval。

(2) HashTable的大小从72下降到56字节,减少22%。

(3) Buckets的大小从72下降到32字节,减少50%。

(4) 数组元素的Buckets的内存空间是一同分配的。

(5) 数组元素的key(Bucket.key)指向zend_string。

(6) 数组元素的value被嵌入到Bucket中。

(7) 降低CPU Cache Miss。

函数调用机制(Function Calling Convention)

PHP7改进了函数的调用机制,通过优化参数传递的环节,减少了一些指令,提高执行效率。

通过宏定义和内联函数(inline),让编译器提前完成部分工作

PHP7在这方面做了不少的优化,将不少需要在运行阶段要执行的工作,放到了编译阶段。例如参数类型的判断(Parameters Parsing),因为这里涉及的都是固定的字符常量,因此,可以放到到编译阶段来完成,进而提升后续的执行效率。

AST(Abstract Syntax Tree,抽象语法树)

AST在PHP编译过程作为一个中间件的角色,替换原来直接从解释器吐出opcode的方式,
让解释器(parser)和编译器(compliler)解耦,可以减少一些Hack代码,同时,让实现更容易理解和可维护。
more -> https://wiki.php.net/rfc/abstract_syntax_tree

这里写图片描述

TLS(Native Thread local storage,原生线程本地存储)

PHP在多线程模式下(例如,Web服务器Apache的woker和event模式,就是多线程),需要解决“线程安全”(TS,Thread Safe)的问题,因为线程是共享进程的内存空间的,所以每个线程本身需要通过某种方式,构建私有的空间来保存自己的私有数据,避免和其他线程相互污染。而PHP5采用的方式,就是维护一个全局大数组,为每一个线程分配一份独立的存储空间,线程通过各自拥有的key值来访问这个全局数据组。

而这个独有的key值在PHP5中需要传递给每一个需要用到全局变量的函数,PHP7认为这种传递的方式并不友好,并且存在一些问题。因而,尝试采用一个全局的线程特定变量来保存这个key值。

Combined comparison Operator (<=>) 结合比较运算符 (<=>)

// PHP 7之前的写法:比较两个数的大小  
function order_func($a, $b) {  
    return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);  
}  
// PHP新增的操作符 <=>,perfect  
function order_func($a, $b) {  
    return $a <=> $b;  
}  

Return Type Declarations 返回类型声明 和Scalar Type Declarations 标量类型声明

PHP语言一个非常重要的特点就是“弱类型”,它让PHP的程序变得非常容易编写,新手接触PHP能够快速上手,不过,它也伴随着一些争议。支持变量类型的定义,可以说是革新性质的变化,PHP开始以可选的方式支持类型定义。除此之外,还引入了一个开关指令declare(strict_type=1);,当这个指令一旦开启,将会强制当前文件下的程序遵循严格的函数传参类型和返回类型。

declare(strict_type=1);
function add(int $a, int $b): int{
    return $a + $b
}

如果不开启strict_type,PHP将会尝试帮你转换成要求的类型,而开启之后,会改变PHP就不再做类型转换,类型不匹配就会抛出错误。对于喜欢“强类型”语言的同学来说,这是一大福音。
更为详细的介绍:
https://wiki.php.net/rfc/scalar_type_hints_v5
PHP7标量类型声明RFC

新特性语法

标量类型声明

标量类型声明 有两种模式: 强制 (默认) 和 严格模式。 现在可以使用下列类型参数(无论用强制模式还是严格模式): 字符串(string), 整数 (int), 浮点数 (float), 以及布尔值 (bool)。它们扩充了PHP5中引入的其他类型:类名,接口,数组和 回调类型。

<?php
// Coercive mode
function sumOfInts(int ...$ints)
{
    return array_sum($ints);
}

var_dump(sumOfInts(2, '3', 4.1));

输出:int(9)
要使用严格模式,一个 declare 声明指令必须放在文件的顶部。这意味着严格声明标量是基于文件可配的。 这个指令不仅影响参数的类型声明,也影响到函数的返回值声明(参见 返回值类型声明, 内置的PHP函数以及扩展中加载的PHP函数)
完整的标量类型声明文档和示例参见
http://php.net/manual/zh/functions.arguments.php#functions.arguments.type-declaration

返回值类型声明

PHP 7 增加了对返回类型声明的支持。 类似于参数类型声明,返回类型声明指明了函数返回值的类型。可用的类型与参数声明中可用的类型相同。

<?php

function arraysSum(array ...$arrays): array
{
    return array_map(function(array $array): int {
        return array_sum($array);
    }, $arrays);
}

print_r(arraysSum([1,2,3], [4,5,6], [7,8,9]));

输出:

Array
(
[0] => 6
[1] => 15
[2] => 24
)

null合并运算符

由于日常使用中存在大量同时使用三元表达式和 isset()的情况, 我们添加了null合并运算符 (??) 这个语法糖。如果变量存在且值不为NULL, 它就会返回自身的值,否则返回它的第二个操作数。

<?php
// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

// Coalesces can be chained: this will return the first
// defined value out of $_GET['user'], $_POST['user'], and
// 'nobody'.
$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';
?>

Spaceship operator 结合比较运算符 (<=>)

<?php
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
?>

Constant arrays using define() 的常量数组

现在可以用 define() 定义数组常量。在 PHP 5.6,只可以被定义与 const。

<?php
define('ANIMALS', [
    'dog',
    'cat',
    'bird'
]);

echo ANIMALS[1]; // outputs "cat"
?>

Anonymous classes 匿名类

通过新的类添加了用于匿名类的支持。这些可以用于代替完整的类定义一次性的对象

<?php
interface Logger {
    public function log(string $msg);
}

class Application {
    private $logger;

    public function getLogger(): Logger {
         return $this->logger;
    }

    public function setLogger(Logger $logger) {
         $this->logger = $logger;
    }
}

$app = new Application;
$app->setLogger(new class implements Logger {
    public function log(string $msg) {
        echo $msg;
    }
});

var_dump($app->getLogger());
?>

输出:

object(class@anonymous)#2 (0) {
}

Unicode codepoint escape syntax Unicode编码转移语法

这 Unicode 编码以十六进制格式,并输出该编码在 UTF-8 为双引号括起来的字符串或定界符。任何有效的编码被接受。

echo "\u{aa}";
echo "\u{0000aa}";
echo "\u{9999}";

Closure::call()

<?php
class A {private $x = 1;}

// Pre PHP 7 code
$getXCB = function() {return $this->x;};
$getX = $getXCB->bindTo(new A, 'A'); // intermediate closure
echo $getX();

// PHP 7+ code
$getX = function() {return $this->x;};
echo $getX->call(new A);

发表评论

电子邮件地址不会被公开。 必填项已用*标注

昵称 *