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:使用类常量以获得最佳性能(请参阅答案末尾).
让我们看看不同版本的性能特征(以及原因):
静态属性中的数组是在编译时非常快速地创建的,无需VM的参与.虽然访问静态属性比访问普通变量要慢一些,但仍然比在每次运行时重新创建数组要快得多.
无论如何,在每次运行时,都会在运行时重新创建正常函数中的数组.在VM中运行时创建意味着每个元素在各个操作码中逐个添加,这意味着相当多的开销(特别是如果数组大于1-2个元素).
正常函数[通常]中的数组创建速度稍快一些,因为通常会加速数组创建(HashTable处理中的优化).如果它是所有常量值,则将其缓存在内部常量值数组中,但在每次访问时重复.但是,执行直接高度专业化的复制操作显然比在PHP 5中逐个向数组添加元素更快.
Opcache在内部将它们标记为IMMUTABLE,允许直接访问[因此您可以使用opcache获得全速].(另见https://blog.blackfire.io/php-7-performance-improvements-immutable-arrays.html)
数组本身总是缓存在内部常量值数组中,具有写时复制语义.
现在使用静态属性比较慢,因为查找静态属性的性能低于对变量的简单写入.[直接访问变量没有额外的开销.]
另请注意,自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)
).
这里有一些基准测试的帖子:
最后一个相当陈旧,但我认为这个比例是成立的.
所以总结一下.当你使用isset
搜索时间是恒定的(它实际上可能会有所不同,但这可以忽略).当您使用in_array
搜索时间取决于元素位置(等数组大小).即使在小阵列上isset
工作也更快.
如果它们永远不会改变那么你应该格式化为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)
一些说明:
define
编译时一样,但是比定义和变量数组快一点.一句话内容:类常量可能更快,但内存可能无关紧要,使用依赖注入设计模式将更具内存效率和灵活性.
虽然类常量或静态属性比在函数中创建数组要快(参见bwoebi的答案),因为它内置一次并且可以多次访问,但它绝不是最有效的方法,或推荐的方法解决OP旨在解决的根本问题.
如果您确定将来不会有任何数据发生变化,或者您永远不想在不同时间使用不同的数据集,即使是进行测试,那么无论如何您都可以使用此方法.如果您想要更灵活的代码,类常量或静态属性可能会导致一些严重的问题.正如我稍后将解释的那样,使用或保存的内存量不太可能重要.更重要的考虑因素是:
- 将来修改我的代码有多容易?
- 我的代码对于不断变化的环境有多灵活
- 单元测试我的代码有多容易?
在提交内存效率最高的路由之前,请务必平衡其他形式的效率,例如开发和调试时间的效率.
由于现代计算机的速度,两个版本之间的性能打击应该很少有所作为.磁盘I/O通常是一个问题而不是内存.如果您的服务器在非常少的内存上运行并且您期望非常高的容量,那么代码的内存效率将比您具有适中的内存和适度的内存更重要.
为了正确看待问题,请参阅这篇关于PHP中数组效率的文章.外卖?尽管PHP5阵列的效率非常低,但即使是100,000个整数的阵列也需要大约1400万个.这是很多,但考虑到平均PHP脚本的内存限制为128M,并且最小服务器建议需要大约2 GB的内存,这突然看起来不同.
这意味着如果代码的其余部分效率低下,或者与低内存相比具有高容量,则应该担心这一点.这将导致您的应用程序速度变慢和/或系统崩溃.
无论如何,在您从一开始就探索建筑选择的情况下,我强烈推荐一种设计模式.即,依赖注入设计模式.这有很多原因,包括代码灵活性和单元测试,但也有一个友好的内存占用.因此,它可能被认为是您推荐的两个选项中的任何一个的最佳实践.
一开始,最简单的方法是使用静态属性.但是,根据我的经验,最简单的路线并不总是最好的路线,而且经常是最难维护的路线.这里的一个问题是你的函数/方法可能会调用其中的另一个类.举个例子,让我们创建两个类:MyFooClass
和DoStuff
,看看它们默认如何交互.
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()
.
有了这种设计模式: