将多次访问的具有常量值的数组放在哪里?

Posted

技术标签:

【中文标题】将多次访问的具有常量值的数组放在哪里?【英文标题】:Where to put arrays with constant value that will be accessed many times? 【发布时间】:2016-11-30 20:55:05 【问题描述】:

我有一些数组存储一些 3D 打印机命令的可能参数。我用它来检查命令是否合法。我对应该将这些数组放在哪里感到困惑。这些数组只会在 formatcheck 函数中被访问,并且该函数将被多次调用,因为有 数千 条命令要检查。我应该将这些作为变量放在 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, $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;
   

或:

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, $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;

【问题讨论】:

这是一个偏好问题。但我会亲自将其设置为类属性。 是否存在一些性能问题。 没有。但第二个过程可能需要更多的处理时间。 没有。设置为属性并仅在每次调用 checkFileGcodeFormat 时调用页面加载或变量 @ChrysUgwu “但第二个过程可能需要更多的处理时间。”我测量了两个版本,发现第二个(具有静态属性)比第一个(php 5.5.36)快两倍 【参考方案1】:

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;

【讨论】:

【参考方案2】:

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

但我想再提一点。如果您有相当大的数组要在其中查找值,更重要的是如何构造它们。我建议这样做:

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

有了这个结构,你可以使用isset (O(1)) 而不是in_array (O(n))。

以下是关于issetin_array 的其他一些问题:

what is faster: in_array or isset? [closed]

in_array vs isset - perfomance

这里有一些带有基准的帖子:

http://maettig.com/1397246220 Tip Of The Day: isset() vs. in_array()

最后一个比较老,但我认为这个比例成立。

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

【讨论】:

很好!现在好多了。 好建议(+1)......即使它没有直接回答手头的问题(where => 看到我的答案),它可能对 OP 使用有帮助in_array.【参考方案3】:

一句话总结:类常量可能更快,但内存可能无关紧要,使用Dependency Injection 设计模式将更加高效和灵活。

虽然类常量或静态属性会比在函数中创建数组更快(请参阅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 岁。我认为通常此类类具有静态方法,因此是无状态的,因此您可能无论如何都不会使用 DI。只需引用静态类的值MyBarClass::foo()。如果您需要实例化该类,我会问为什么这是一个要求,并且可能会回到 DI。即使是一个日志类,虽然它可能可以被实例化,但应该非常仔细地设计成封装而不是直接注入。 所以粗略的经验法则是 DI 主要应用于实例(和变量),而不是无状态类【参考方案4】:

如果他们永远不会改变,那么你应该格式化为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)) 
    ...

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

一些注意事项:

const 与 define 类似,但在编译时加入,因此比定义和变量数组稍快。 您可以在全局级别或类级别 (http://php.net/manual/en/language.oop5.constants.php) 定义 const。从 php 7.1 开始,您还可以将类 const 声明为私有/公共/受保护等。 我使用大写字母来定义,这是一个非官方标准,但不是要求。

【讨论】:

我认为你的回答结合@sevavietl 会好很多,是吗? 确实:作为一般规则,也使用isset()。没有专门用 consts 测试速度,但你可以做 const VAR = [23 => true, 24 => true]; 等,所以应该很容易测试。 嗯,这对我在回答中所说的话有什么补充吗?还是我错过了什么? @bwoebi - 我没有看到你关于 const 的脚注,抱歉。为什么不从答案开始(使用 const),然后继续寻找性能较差的替代方案? const 在类之外的时间也比 5.6 长,这比在类中构建的性能更高(你仍然可以在同一个文件中声明,尽管它是一个静音点,因为你现在应该在 5.6 上)。但简单的答案是我在阅读您的文章时错过了它。 @Robbie 没问题,好点;我添加了一个小的 TL;DR。此外,虽然 const 已经超过 5.6,但您只能将数组分配给 5.6 的常量。它甚至无法在 5.5 及以下版本中编译。【参考方案5】:

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

What is Big-O? Big-O cheat sheet

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

class foo

    protected static $bar = array();

【讨论】:

更多关于喜欢静态日期的信息将不胜感激! Big-O 表 +1。谢谢! 你应该说为什么使用静态属性? (基准?关于内部的知识?)……Big-O 与此无关……【参考方案6】:
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;

【讨论】:

以上是关于将多次访问的具有常量值的数组放在哪里?的主要内容,如果未能解决你的问题,请参考以下文章

我在哪里放置一个数组,以便 Windows 窗体控件可以访问它?

我应该将使用 DDD 和 CQRS 方式刷新访问令牌的代码放在哪里?

PHP array_intersect + array_flip 具有多次值的数组

Java - 将应用程序数据放在哪里?

我应该把我的 .jasper 文件放在哪里以及如何访问它? [复制]

在 bash 中,是不是可以将多个数组放在引号内然后访问它们?