在哪里放置具有常量值的数组,可以多次访问?

Kri*_*ofe 32 php arrays performance static function

我有一些数组存储一些3D打印机命令的可能参数.我用它来检查命令是否合法.我很困惑我应该把这些数组放在哪里.这些数组只能在formatcheck函数中访问,并且由于需要检查数千个命令,因此将多次调用该函数.我应该将这些作为变量放在formatcheck函数中,还是在格式检查函数所在的类的开头,作为私有静态变量?

public function checkFileGcodeFormat()
{
    $Ms = array(82, 83, 84, 104, 106, 107, 109, 140, 190);
    $Gs = array(0, 1, 20, 21, 28, 90, 91, 92);
    $Ts = array(0, 1);
    if (! ($this->hasM() && $this->hasNoXYZ() && in_array($this->M, $Ms)) || ($this->hasG() && in_array($this->G, $Gs)) || ($this->hasT() && $this->hasNoXYZ() && in_array($this->T, $Ts)) )
        return false;
    else
        return true;
}   
Run Code Online (Sandbox Code Playgroud)

要么:

private static $Ms = array(82, 83, 84, 104, 106, 107, 109, 140, 190);
private static $Gs = array(0, 1, 20, 21, 28, 90, 91, 92);
private static $Ts = array(0, 1);
...
...
public function checkFileGcodeFormat()
{
    if (! ($this->hasM() && $this->hasNoXYZ() && in_array($this->M, self::$Ms)) || ($this->hasG() && in_array($this->G, self::$Gs)) || ($this->hasT() && $this->hasNoXYZ() && in_array($this->T, self::$Ts)) )
        return false;
    else
        return true;
}
Run Code Online (Sandbox Code Playgroud)

bwo*_*ebi 32

TL; DR:使用类常量以获得最佳性能(请参阅答案末尾).

让我们看看不同版本的性能特征(以及原因):

PHP 5

静态属性中的数组是在编译时非常快速地创建的,无需VM的参与.虽然访问静态属性比访问普通变量要慢一些,但仍然比在每次运行时重新创建数组要快得多.

无论如何,在每次运行时,都会在运行时重新创建正常函数中的数组.在VM中运行时创建意味着每个元素在各个操作码中逐个添加,这意味着相当多的开销(特别是如果数组大于1-2个元素).

PHP 7.0

正常函数[通常]中的数组创建速度稍快一些,因为通常会加速数组创建(HashTable处理中的优化).如果它是所有常量值,则将其缓存在内部常量值数组中,但在每次访问时重复.但是,执行直接高度专业化的复制操作显然比在PHP 5中逐个向数组添加元素更快.

Opcache在内部将它们标记为IMMUTABLE,允许直接访问[因此您可以使用opcache获得全速].(另见https://blog.blackfire.io/php-7-performance-improvements-immutable-arrays.html)

PHP 7.1

数组本身总是缓存在内部常量值数组中,具有写时复制语义.

现在使用静态属性比较慢,因为查找静态属性的性能低于对变量的简单写入.[直接访问变量没有额外的开销.]


另请注意,自PHP 5.6起,您可以使用数组的值声明(类)常量.PHP 7.1允许直接替换同一个类的类常量,并将数组直接添加到内部常量值数组,以便直接用于in_array.

即最快的代码(至少7.1):

private const Ms = array(82, 83, 84, 104, 106, 107, 109, 140, 190);
private const Gs = array(0, 1, 20, 21, 28, 90, 91, 92);
private const Ts = array(0, 1);
...
...
public function checkFileGcodeFormat()
{
    if (! ($this->hasM() && $this->hasNoXYZ() && in_array($this->M, self::Ms)) || ($this->hasG() && in_array($this->G, self::Gs)) || ($this->hasT() && $this->hasNoXYZ() && in_array($this->T, self::Ts)) )
        return false;
    else
        return true;
}
Run Code Online (Sandbox Code Playgroud)


sev*_*etl 10

我认为定义数组属性更有意义,因为方法内定义的数组是在每次调用时创建的.

但我想说明一点.如果您有相当大的数组来查找值,那么构建它们的方式就更为重要.我建议这样:

array(
    82 => true,
    83 => true,
    84 => true,
    104 => true,
    106 => true,
    107 => true,
    109 => true,
    140 => true,
    190 => true
);

array(
    0 => true,
    1 => true,
    20 => true,
    21 => true,
    28 => true,
    90 => true,
    91 => true,
    92 => true
);

array(
    0 => true,
    1 => true
);
Run Code Online (Sandbox Code Playgroud)

拥有此结构,您可以使用isset(O(1))而不是in_array(O(n)).

以下是关于issetvs.的其他一些问题in_array:

这里有一些基准测试的帖子:

最后一个相当陈旧,但我认为这个比例是成立的.

所以总结一下.当你使用isset搜索时间是恒定的(它实际上可能会有所不同,但这可以忽略).当您使用in_array搜索时间取决于元素位置(等数组大小).即使在小阵列上isset工作也更快.


Rob*_*bie 5

如果它们永远不会改变那么你应该格式化为const.在编译时有烘焙,因此将是最快的.

const MS = [82, 83, 84, 104, 106, 107, 109, 140, 190];
const GS = [0, 1, 20, 21, 28, 90, 91, 92];
const TS = [0, 1];

if (!in_array($this->M, MS)) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

要么

class () {
    const MS = [82, 83, 84, 104, 106, 107, 109, 140, 190];
    const GS = [0, 1, 20, 21, 28, 90, 91, 92];
    const TS = [0, 1];

    if (!in_array($this->M, self::MS)) {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

一些说明:

  • const就像define编译时一样,但是比定义和变量数组快一点.
  • 您可以在全局级别或类级别定义const(http://php.net/manual/en/language.oop5.constants.php).从php 7.1开始,你也可以将类consts声明为private/public/protected等.
  • 我使用大写字母来定义,这是非官方的标准,但不是要求.


smc*_*nes 5

一句话内容:类常量可能更快,但内存可能无关紧要,使用依赖注入设计模式将更具内存效率和灵活性.

虽然类常量或静态属性比在函数中创建数组要快(参见bwoebi的答案),因为它内置一次并且可以多次访问,但它绝不是最有效的方法,或推荐的方法解决OP旨在解决的根本问题.

如果您确定将来不会有任何数据发生变化,或者您永远不想在不同时间使用不同的数据集,即使是进行测试,那么无论如何您都可以使用此方法.如果您想要更灵活的代码,类常量或静态属性可能会导致一些严重的问题.正如我稍后将解释的那样,使用或保存的内存量不太可能重要.更重要的考虑因素是:

  • 将来修改我的代码有多容易?
  • 我的代码对于不断变化的环境有多灵活
  • 单元测试我的代码有多容易?

在提交内存效率最高的路由之前,请务必平衡其他形式的效率,例如开发和调试时间的效率.

为什么记忆可能无关紧要

由于现代计算机的速度,两个版本之间的性能打击应该很少有所作为.磁盘I/O通常是一个问题而不是内存.如果您的服务器在非常少的内存上运行并且您期望非常高的容量,那么代码的内存效率将比您具有适中的内存和适度的内存更重要.

为了正确看待问题,请参阅这篇关于PHP中数组效率的文章.外卖?尽管PHP5阵列的效率非常低,但即使是100,000个整数的阵列也需要大约1400万个.这是很多,但考虑到平均PHP脚本的内存限制为128M,并且最小服务器建议需要大约2 GB的内存,这突然看起来不同.

这意味着如果代码的其余部分效率低下,或者与低内存相比具有高容量,则应该担心这一点.这将导致您的应用程序速度变慢和/或系统崩溃.

无论如何,在您从一开始就探索建筑选择的情况下,我强烈推荐一种设计模式.即,依赖注入设计模式.这有很多原因,包括代码灵活性和单元测试,但也有一个友好的内存占用.因此,它可能被认为是您推荐的两个选项中的任何一个的最佳实践.

为什么不是静态属性

一开始,最简单的方法是使用静态属性.但是,根据我的经验,最简单的路线并不总是最好的路线,而且经常是最难维护的路线.这里的一个问题是你的函数/方法可能会调用其中的另一个类.举个例子,让我们创建两个类:MyFooClassDoStuff,看看它们默认如何交互.

class MyFooClass
{
    public static $Ms = array(82, 83, 84, 104, 106, 107, 109, 140, 190);
    public static $Gs = array(0, 1, 20, 21, 28, 90, 91, 92);
    public static $Ts = array(0, 1);
}

class DoStuff
{
    public function oneOfThousands()
    {
        $array = MyFooClass::$Gs;
        //... do stuff
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您希望为不同目的插入不同的数组值,或者您希望使用更少或更多设置进行单元测试,则会出现复杂情况.

依赖注入救援!

与所有设计模式一样,依赖注入解决了一个问题.在这种情况下,问题是在不牺牲灵活性的情况下容易且有效地在多个函数/方法之间传递值.使用基本DI模式,可以在非静态属性中初始化数组,并将包含此数组属性的单个对象传递给代码的每个部分.这样可以消除您对性能的担忧.

例:

class MyFooClass
{
    private $Ms, $Gs, $Ts;

    public function __construct()
    {
        $this->Ms = array(82, 83, 84, 104, 106, 107, 109, 140, 190);
        $this->Gs = array(0, 1, 20, 21, 28, 90, 91, 92);
        $this->Ts = array(0, 1);
    }

    public function checkFileGcodeFormat()
    {

        if (! ($this->hasM() && $this->hasNoXYZ() && in_array($this->M, $this->Ms)) || ($this->hasG() && in_array($this->G, $this->Gs)) || ($this->hasT() && $this->hasNoXYZ() && in_array($this->T, $this->Ts)) )
            return false;
        else
            return true;
    }
}


// DI here:
$foo = new MyFooClass();

$bar = new MyBarClass();
$bar->setArrays($foo);

//alternative DI approach - parameters in constructor
$bar = new MyBarClass($foo);
Run Code Online (Sandbox Code Playgroud)

在您的中MyBarClass,您将一个MyFooClass对象分配给一个属性$foo.然后,您可以使用此对象调用任何公共方法或属性$this->foo.例如:$this->foo->checkFileGcodeFormat().

有了这种设计模式:

  • 当您想开发新的单元测试时,这样做会容易得多.
  • 如果您想要/需要为应用程序实现Gcode的子集,只需传递具有不同数组值的不同对象.
  • 同样,如果您想在新类上测试新的Gcode而不将其引入脚本的每个部分,您可以.
  • 消耗的内存是PHP中指针的大小(与64位架构中的C ... 8字节中的指针大小相同).

结论

  • 如果可以,我建议使用依赖注入设计模式.
  • 您可以选择静态属性以获得更好的内存占用(注意:这与依赖注入不相互排斥,但如果使用依赖注入则不太重要).
  • 在具有中等流量的标准Web服务器设置中,无论是使用静态属性还是从函数内调用数组,内存消耗都不太重要.