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

时间:2016-07-27 02:53:12

标签: php arrays performance function static

我有一些数组存储某些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;
}   

或:

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;
}

6 个答案:

答案 0 :(得分:30)

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;
}

答案 1 :(得分: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
);

拥有此结构后,您可以使用issetO(1))代替in_arrayO(n))。

以下是有关issetin_array的其他问题:

以下是一些基准测试的帖子:

最后一个相当陈旧,但我认为这个比例很高。

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

答案 2 :(得分:5)

如果它们从未改变,那么您应该格式化为FILE* file = fopen("public.pem","r"); if (file != nullptr) { //This can be changed to this for the same results: //RSA* rsa = PEM_read_RSAPublicKey(file, nullptr, nullptr, nullptr); RSA* rsa = PEM_read_RSA_PUBKEY(file, nullptr, nullptr, nullptr); //etc... } 。在编译时有烘焙,因此将是最快的。

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)) {
    ...
}

一些注意事项:

  • const与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)) { ... } } 类似,但在编译时烘焙,因此比定义和变量数组略快。
  • 您可以在全局级别或类级别(http://php.net/manual/en/language.oop5.constants.php)定义const。从php 7.1开始,你也可以将类consts声明为private / public / protected etc。
  • 我使用大写字母作为非正式标准的定义,但不是要求。

答案 3 :(得分:5)

一句话外卖:类常量可能更快,但内存可能无关紧要,使用Dependency Injection设计模式将更有效,更灵活。< / p>

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

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

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

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

为什么记忆可能无关紧要

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

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

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

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

为什么不是静态属性

一开始,最简单的方法是使用静态属性。但是,根据我的经验,最简单的路线并不总是最好的路线,而且经常是最难维护的路线。这里的一个问题是你的函数/方法可能会调用其中的另一个类。例如,让我们创建两个类: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
    }
}

现在,如果你想为不同的目的插入不同的数组值,或者你想用更少或更多的设置进行单元测试,那么很复杂。

依赖注入救援!

与所有设计模式一样,依赖注入解决了一个问题。在这种情况下,问题是在不牺牲灵活性的情况下容易且有效地在多个函数/方法之间传递值。使用基本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);

MyBarClass中,您要为属性MyFooClass分配$foo个对象。然后,您可以使用$this->foo从此对象调用任何公共方法或属性。例如:$this->foo->checkFileGcodeFormat()

使用这种设计模式:

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

结论

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

答案 4 :(得分:4)

如果您真的想了解如何衡量代码性能,那么您应该熟悉Big-O表示法。

What is Big-O? Big-O cheat sheet

除此之外,将它们定义为静态数据的受保护类基本属性。

class foo
{
    protected static $bar = array();
}

答案 5 :(得分:1)

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;
}