您好,欢迎访问一九零五行业门户网

关于 PHP 中巨型数据对象的内存开销问题的研究

首先请大家不要误会,不是我要发表对这个问题的什么研究成果,而是想请大家帮我一起来分析研究一下这个问题 :)
描述一下简化了的问题背景:在一个用 php 实现的网站中,所有的程序文件都在一开始包含了一个公共的文件 common.php。现在由于业务需要,在 common.php 中定义了一个“巨型”的数据对象(一个含有约 500k 个 int 值的 array 对象),比如 $huge_array = array(1,2,3,...,500000),并且在整个系统中对 $huge_array 只有“读”访问。假设系统需要持续稳定运行在 100 个并发请求的状态下。
问题 1:$huge_array 在 common.php 源文件中大概要占用 10m(这个姑且不算是问题),加载到内存中也许要占用 4m(只是估算一下,至于准确测量其尺寸,不是本文要讨论的要点)。问题在于,php 本身每处理一个 http request,都是要启用一个独立的进程(或者是线程),那它是不是都要重新在内存中加载这个约 4m 的内存块呢?如果不能共享内存的话,那可能就要同时占用近 400m 的物理内存,无论在内存占用量还是内存访问效率方面,都是很不利的。
问题 2:当启用了某种缓存机制(比如 apc、xcache 等)的时候,我们知道这类缓存机制都具有对 opcode 进行缓存的能力,但似乎也只是减少了脚本编译环节的重复性工作,对于运行时的变量加载,是否也能起到共享内存的作用呢?希望它能起到一定的作用,毕竟那一大堆 int 值肯定也是作为 opcode 的一部分而存在的。
问题 3:如果上述借用 xcache 对 opcode 的缓存不能达到目的的话,那我直接操作 xcache 是否会有效呢?就是说,不把 $huge_array 写在 common.php 里,而是写到缓存引擎里。
本文意在通过分析研究,确定这种用法是否会导致内存使用瓶颈,如果有问题的话如何优化。当然,想尽办法减小这个巨型数据对象本身的尺寸是首先最值得考虑的,必须的,但那个属于数据结构和算法方面的话题,就不在本文中讨论了。欢迎大家发表一下自己对这个问题的分析观点,如果能给自己的观点设计一些可操作的测试验证方案就更好了,如果你没时间写代码,只要方案看上去合理,我愿意来写测试代码 ^_^
基于csdn论坛提供的插件扩展功能,自己做了个签名档工具, 分享给大家,欢迎 技术交流 :)
回复讨论(解决方案) 既然是500多k的数据,是不是考虑一下索引,然后分段处理呢,干嘛要全部都加载进来???
1、是的,都要重新在内存中加载
2、既然是 所有的程序文件都包含 那么应该被缓存,因为他也是 opcode 的一部分
3、那就是一件很无聊的事情了,还不如优化数据结构和算法
1、 老大说了
2、 是opcode的一部分,当然要优化
3、 如果编译的时间大于按索引获取数据的时间,那么为什么要编译呢?你编译也仅仅是为了按照key去获取相应的数据,当你有办法解决按key获取数据,为什么一定要用php的array数据的结构呢?这个问题有点像,是从一个txt文件中获取数据,还是从一个包含的php来获取数据?
例如 1.txt 的内容如下
1111
2222
3333
4444
……
1.php 的内容如下
current().'
';?>
测试一个50m左右的xml,需时不到0.01秒行数越大,需时越多
抱歉耽搁了这么久才来跟进这个帖子,这期间在工作之余补习了一下 opcode 相关的知识。
感谢 hnxxwyq 在 #10 楼的回帖,关于 opcode 的解释很有启发性,促使我去找了个用于分析 opcode 的小工具(请参考 vld是个好东西),顺便把 build php under windows 也操练了一遍,hehe。
实践的结果是,对于这段程序:

编译出来的 opcode 是:
number of ops: 10compiled vars: !0 = $huge_array, !1 = $elemline # * op fetch ext return operands--------------------------------------------------------------------------------- 2 0 > ext_stmt 1 init_array ~0 1 2 add_array_element ~0 2 3 add_array_element ~0 3 4 assign !0, ~0 3 5 ext_stmt 6 fetch_dim_r $2 !0, '2' 7 assign !1, $2 4 8 > return 1 9* > zend_handle_exception
这就很容易看清楚了,正如 hnxxwyq 所说,那个“巨型”数据对象,是由一条一条的程序码执行堆砌起来的,也就是说,缓存的只是 opcode 程序码,运行时的数据对象还是要占用新的内存空间。看来 opcode cache 肯定是指望不上了,只能寻求其它的优化方案了。
基于csdn论坛提供的插件扩展功能,自己做了个签名档工具, 分享给大家,欢迎 技术交流 :)
把它搞成内存数据库吧,这样始终在内存里,又不用每次去搞那么大个进程
to snmr_com: 感谢你的热情回帖,我认真地看了 :)
很多时候务虚的思考是必要的,我基本认同你所提的大部分观点。回到我这个具体的问题,既然寄希望于缓存机制来解决内存使用瓶颈的目标落空了,接下来恐怕也只能考虑其它的优化方案了。你在 #13 楼给出的方法挺有创意,hehe。其实如果采用数据文件存储的方案,倒也不一定要把数据保存在 php 程序文件本身里面了,而且“按行定位”的访问方式估计也不如采用二进制存储的方式效率高。我个人更喜欢 shmop 之类的共享内存方案,出于一个成见(也许是偏见),我总觉得内存访问怎么说也比文件访问来得快。这其实是一个很开放的问题了,具体的优化方案肯定是要针对具体的需求的,我在原帖中给出的只是“简化了的问题背景”,完全不足以设计具体的优化方案,anyway,非常感谢你的热情帮助 ^_^
1.上面的程序写在一起只是方便你测试,没看我说测试用了xml,就是说外部文件了
2.快速载入数据我也倾向二进制,但二进制的问题是不能明文搜索,要搜索就要整体载入了,但splfileobject全文搜索是不需要整体载入的跟其他例如sscan等函数结合使用
3.不是我的创意,我是学习spl过程中见到洋人讨论如何paser一个1g的sql文件所用到的方案,借花献佛
csdn 对连续回帖有限制,这里对前面几位朋友的回帖一并回应,见谅 :)
既然是500多k的数据,是不是考虑一下索引,然后分段处理呢,干嘛要全部都加载进来???
这个可以在设计优化方案的时候考虑,多谢!
首先,把这个玩意儿放memcache吧
需要的地方去取,就这样,就够了
memcache 的编程接口很简洁,作为一种数据存储方案,这是它的优势。但用在我说的这个问题背景下,似乎并没有解决主要矛盾。
你可以把这个巨型的数据块放到客户端来.
用json的方法来做
……
这个方法会有更适合它的场景,具体到我这个问题来说,并不合适。因为我要用到这个“巨型”数据对象的地方是“服务端业务逻辑”,而且,10m 的数据作为网页的一部分传给浏览器也不合适 :)
4m,咔咔,不算太大
把它简化成一个值, 然后测测你目前的运行的php的内存峰值比比看....
单独一个 4m 是不大,问题我现在考虑的是在 100 并发下,如果不能“共享”的话,就将是 400m,而且每次对这些内存区块的构建和销毁也是个不容忽略的开销。
把它搞成内存数据库吧,这样始终在内存里,又不用每次去搞那么大个进程
是一个思路,貌似 nosql 方案也可以解决类似的问题。
如果apc缓存不了
用memcache应是比较好的方案了
1.上面的程序写在一起只是方便你测试,没看我说测试用了xml,就是说外部文件了
2.快速载入数据我也倾向二进制,但二进制的问题是不能明文搜索,要搜索就要整体载入了,但splfileobject全文搜索是不需要整体载入的跟其他例如sscan等函数结合使用
3.不是我的创意,我是学习spl过程中见到洋人讨论如何paser一个1g的sql文件所用到的方案,借花献佛
spl 里面有一堆宝贝,以前还真没留意,学习了!
想不一下子都放在内存里,就排序后放到文件里。需要搜索时,用二分法进行搜索。
spl的那个方法应该大概也是如此。
感谢大家的参与,我想我最初的问题基本已经有答案了。
对 #6 楼说声抱歉,csdn 的结贴界面不知是什么逻辑,你那个回帖不让给分 :(
编译成extension
其它类似信息

推荐信息