从阵列移动到课程 阵列永远

自从我开始使用PHP以来,阵列一直是我的朋友。它们是多功能的,它们具有各种功能,它们易于使用。我在版本之后使用它们,甚至使用PHP 7.2,我仍然依赖于它们很多。多年来,课程也进入了我的工具集。它们有不同的用法:类是复杂的数据结构,适用于业务逻辑。简单的数据结构获取数组。直到我们尝试了什么似乎不可能:从数组移动到一个类。

类比PHP中的数组更有效

我遇到了另一个课程,课程越来越好于php 7.这来自 Nikita Popov.,所以这是严肃的。我仔细看看,跑进了这一点 有趣的文章,来自同一作者:。阵列和类在内部基本相同:属性列表。类包含此属性列表和类名,用于定义。另一方面,阵列定义了自己的键和值。他们为每次发生而做,而课程联邦的属性定义,只做一次。

基本上,在创建10次时[‘a’ =>1]; PHP创建10个字符串‘a’。同时实例化A级{PUBLIT $ A; 10次​​,意味着‘a’仅创建一次。后来,PHP将属性识别为‘number 0’并保存属性名称的长度。使这个例子明显,更换‘a’ by ‘a_really_long_but_still_valid_php_name_for_a_property.’并且节省了明显。

用课程替换数组

如何将PHP的此功能与我的优势一起使用?显然,用类替换数组会保存内存。属性的数量必须是很大的。从Nikita读取图表,任何尺寸都会提供增益,但小,较小。因此,排除了5个属性的小阵列。此外,并且可能更重要的是,阵列的使用必须高:理想情况下,循环中的数组是主要候选。

My first check was a quick check, with a simple micro-optimization script. How do 10000 array creations compare to 10000 objects instantiation ? Check the script on //3v4l.org/AiZN2. The results are simple : objects are usually 30 % faster, and they use half the memory. Both results are interesting, and significant. They are also a bit variable.

在代码中的目标

现在,我们所需要的只是一个很好的候选人。数组,使用超过5个属性,并在循环中创建。这些条件排除了大量选项阵列,这些阵列是在某处创建的,以后使用一次,以进行配置。它还排除用作循环基础的数组:那些应该变成发电机。

在核心 exakat. 引擎,装载机负责查看所有令牌,并将它们放在Gremlin数据库中。它将PHP脚本转换为令牌数组,并为那些用于插入的令牌准备。同时,它收集了由PHP标记器间接提供的几个信息。 PHP可能会显示一个是一个变量的令牌,但实际上是属性定义(私有$ var),或参数(foo($ arg)),或变量名称($$ var)。 Loader收集要构建的图形的那种信息。

丢弃了大约2三分之一的PHP牌牌读取的令牌。令牌的良好三分之一实际上是无用的,就像白色空间,评论等。第二个三分之一是分隔符,如()或;这对解析并理解PHP代码很重要,但不用静态分析。令牌的最后三分之一是有趣的。 BordPress这样的项目有大约2亿个代币,EZ发布有4,4百万,最大(Wikia)的最大有1100万。

数组以及它在代码中的出现方式

目前,将在图中插入的令牌由数组处理。最初,那个’S PHP如何在调用token_get_all()时提供令牌描述。此函数返回阵列数组。令牌被描述为字符串,如括号或括号,但大多为包含实际令牌,其常量名称和行号的数组。

处理令牌,并完成其他信息。每个令牌始终提供行号和代码,以及‘fullcode’,这是一种重建当前结构与支持性令牌。例如,您希望看到$阵列[‘index’]在全码中,而当前令牌只能持有不表征性‘$array’.

根据令牌类型计算许多属性。例如,字符串获取他们的分隔符发现:它可能是‘或«或者没有。这一点’t应用于变量名称。全局变量通过$减少了他们的名称,以便在$全球数组中找到。课程获取其完全命名空间的名称。所有这些属性都取决于令牌,并且在不需要时才会省略它们。这也是阵列的强度:较少。

这导致了很多isset()测试。有时,父令牌读取属性。例如,当检查是表达式时,父令牌必须检查其所有子项是否具有常量属性,以及此属性设置为true。事实上,ISSET()的提高用法是数据结构未调整的标志。

按类替换数组

用课程替换数组很容易。该阵列充当容器,与其内部数据有关的很少。所需的处理是检查与ISSET()的存在。这可以处理默认值。因此,替换类没有任何方法,也没有常量:只有公共属性。属性名称是来自数组的索引。

使用__set()收集所有属性名称

该阶段的主要问题是找到所有属性。需要首先定义该类,然后实例化。当然,阵列的惯例的性质意味着没有任何索引的中央储存库都可提供。我是懒惰的,我在飞行中收集了他们。我使用了魔法方法__set()来帮助我: __放()在使用属性但未定义时调用。我写了它,所以它会警告我在替换类中的未定义属性。运行单元测试现在意味着收集缺少的属性名称。

使用类作为数组的一个优点是类允许默认值。阵列创建为空,并且需要显式设置默认值。通过移动到类,具有属性的默认值,可以删除将数组索引设置为默认值的每一行。我可以在一类4K LOC上左右左转。那’净增益,总是好事。

代码的其他良好减少是$阵列的转换[‘fullcode’] to $array->全代码。这也是无害的,但第一个表达是4令牌,第二个是3.代码读取,写入较短,并且可以更快地解析PHP。它’■只有在这里,因为这没有衡量,并且没有’与加工时间相比意味着很多。

用常量进行比较替换ISSET

所有ISSET()呼叫现在已过时,因为类将始终使它们设置,有用。因此,isset()被猎杀并用比较替换。所以,而不是‘isset($array[‘intval’])’,我现在可以读取$阵列 - >Intval === self :: no_intval;再次,代码更加可读,虽然它更长。

最终警告

在短暂的时刻,当我到达这条线时,我后悔所有有希望的新代码:

<?php
  fputcsv($fp, $array) ;
?>

Fputcsv.()是一个需要数组的本机PHP函数。没有办法给它一个对象。第一个替代方法感觉像叛国罪:它是一个转换为(阵列),它一切顺利。它甚至比阵列更好。实际上,通过所有属性定义先验,它们会保持相同的顺序,无论他们处理的方式如何。这意味着某些用于确保在右侧CSV文件中推送了良好标题的某些排序代码现在可以转到。

最后,将铸件移动到Array-Class的方法:一些CSV导出需要一个简短的列列表而不是所有的列表。一种方法是将类转换为某些特定格式的完美位置。最初,Array-Class是构建的,以是阵列的替代方法,以及较少的方法。当需要时,它似乎非常方便地提取一致的信息。它也是存储默认使用的常量的好地方。虽然它没有’t申请此处,向每个属性添加Serverer也可以添加额外的数据检查到类。

可读性

首先,PHP代码更可读且更缩短。由于较短的语法:208k至187k,整个班级丢失了约10%的代码大小。这包括从isset()移动到比较。代码本身并没有’T完全改变:没有新功能,没有清洁。只是较小的代码。那’s a first win.

表演

在一个小型的PHP项目上运行它,Exakat引擎花了20多岁,现在需要18秒,所以2秒少。那 ’S 10%的速度增益,在真实世界中。我们没有’t单独测量类上的实际增益,但在整个应用程序内,包括所有其他合作类。除了装载机,那些课程没有’T获得任何重构,看到没有收获。

记忆

内存消耗从45MB下降到25MB。这比内存保存的减少约为45%。这种负载甚至更好,较小的负载不太有趣:赢得了一些恒定的开销’逃离了。尽管如此,重构很重要。

用课程替换数组

用课程替换阵列是一个有趣的行动,以及几个层次的令人惊讶的兴趣。以下是从该实验中值得记住的东西清单:

  • 在仅限常量接口之后,仅限静态方法的特征,只属性类是要了解的工具。避免滥用它。
  • 仅属性的类是一个值得替换的数组。不应使用Array_ * PHP函数(以及排序,并屏蔽......)处理此类替换的一个很好的阵列候选者。理想情况下,它是在循环中创建的,并且具有许多属性。
  • 移动到类意味着将默认值设置为所有值。这是移动的另一个有效案例:更换 发行()具有可读的比较,每次创建数组时都会避免明确的默认值。
  • Magic方法__set()是一个很好的工具,在开发期间,警告未定义的属性。这是具有静态分析的未定义属性的动态分析。使用简单的功能__set($名称,$ value){echo“$名称未设置为”.__类__。“\ n”;在一个类中,您可以使用与其初始目的相反的目标使用__set()。
  • 并非所有阵列都应该变成课程。很少使用数组和小阵列可能不值得转变为一堂课。它需要时间,并赢了’表演表演。
  • 将数组转换为类是将其与方法和常量扩展的第一步。为内存做,留下来的功能。

这里提出的想法在exakat引擎中成功应用了PHP的智能审计工具。立即下载并在推特上关注我们。