ホームページ >バックエンド開発 >PHPチュートリアル >PHPメモリ
PHP数组读取的循环操作
附:
PHP メモリ オーバーフローの解決策
PHP内存溢出Allowed memory size of 解决办法使用脚本语言最大的好处之一就是可利用其拥有的自动垃圾回收机制(释放内存)。你不需要在使用完变量后做任何释放内存的处理,PHP会帮你完成。当然,我们可以按自己的意愿调用 unset() 函数来释放内存,但通常不需要这么做。不过在PHP里,至少有一种情况内存不会得到自动释放,即便是手动调用 unset()。详情可考:http://bugs.php.net/bug.php?id=33595。问题症状如果两个对象之间存在着相互引用的关系,如“父对象-子对象”,对父对象调用 unset() 不会释放在子对象中引用父对象的内存(即便父对象被垃圾回收,也不行)。有些糊涂了?我们来看下面的这段代码:查看源码打印?01 <?php 02 class Foo { 03 function __construct() 04 { 05 $this->bar = new Bar($this); 06 } 07 } 08 09 class Bar { 10 function __construct($foo = null) 11 { 12 $this->foo = $foo; 13 } 14 } 15 16 while (true) { 17 $foo = new Foo(); 18 unset($foo); 19 echo number_format(memory_get_usage()) . "\n"; 20 } 21 ?> 运行这段代码,你会看到内存使用率越来越高越来越高,直到用光光。...33,551,61633,551,97633,552,33633,552,696PHP Fatal error: Allowed memory size of 33554432 bytes exhausted(tried to allocate 16 bytes) in memleak.php on line 17对大部分PHP程序员来讲这种情况不算是什么问题。可如果你在一个长期运行的代码中使用到了一大堆相互引用的对象,尤其是在对象相对较大的情况下,内存会迅速地消耗殆尽。Userland解决方案虽然有些乏味、不优雅,但之前提到的 bugs.php.net 链接中提供了一个解决方案。这个方案在释放对象前使用一个 destructor 方法以达到目的。Destructor 方法可将所有内部的父对象引用全部清除,也就是说可以将这部分本来会溢出的内存释放掉。以下是“修复后”的代码:查看源码打印?01 <?php 02 class Foo { 03 function __construct() 04 { 05 $this->bar = new Bar($this); 06 } 07 function __destruct() 08 { 09 unset($this->bar); 10 } 11 } 12 13 class Bar { 14 function __construct($foo = null) 15 { 16 $this->foo = $foo; 17 } 18 } 19 20 while (true) { 21 $foo = new Foo(); 22 $foo->__destruct(); 23 unset($foo); 24 echo number_format(memory_get_usage()) . "\n"; 25 } 26 ?> 注意那个新增的 Foo::__destruct()方法,以及在释放对象前对 $foo->__destruct() 的调用。现在这段代码解决了内存使用率一直增加的问题,这么一来,代码就可以很好的工作了。PHP内核解决方案?为什么会有内存溢出的发生?我对PHP内核方面的研究并不精通,但可以确定的是此问题与引用计数有关系。在 $bar 中引用 $foo 的引用计数不会因为父对象 $foo 被释放而递减,这时PHP认为你仍需要 $foo 对象,也就不会释放这部分的内存……大概是这样。这里确实可以看出我的无知,但大体意思是:一个引用计数没有递减,所以一些内存永远得不到释放。在前面提到的 bugs.php.net 链接中我看到修改垃圾回收的过程将会牺牲极大的性能,因为我对引用计数了解不多,所以我认为这是真的。与其改变垃圾回收的过程,为什么不用 unset() 对内部对象做释放的工作呢?(或者在释放对象的时候调用 __destruct()?)也许PHP内核开发者可以在此或其他地方,对这种垃圾回收处理机制做出修改。更新:Martin Fjordvald 在评论中提到了一个由 David Wang 为垃圾回收所写的补丁(其实它看起来更像“一整块布”??非常巨大。详情参见此邮件结尾的CVS导出信息。)确实存在(一封邮件),并受到了PHP内核开发成员的关注。问题是这个补丁要不要放到PHP5.3中并未得到太多支持 。我觉得一个不错的折中方案就是在 unset() 函数中调用对象中的 __destruct() 方法;の許容メモリ サイズ MySQL に大量のデータをクエリする PHP のメモリ使用量分析
PHP查询MySQL大量数据的内存占用分析作者:ideawu 出处:博客2011-07-07 14:32昨天, 有同事在PHP讨论群里提到, 他做的一个项目由于MySQL查询返回的结果太多(达10万条), 从而导致PHP内存不够用. 所以, 他问, 在执行下面的代码遍历返回的MySQL结果之前, 数据是否已经在内存中了? 这篇文章主要是从原理, 手册和源码分析在PHP中查询MySQL返回大量结果时, 内存占用的问题, 同时对使用MySQL C API也有涉及. 昨天, 有同事在PHP讨论群里提到, 他做的一个项目由于MySQL查询返回的结果太多(达10万条), 从而导致PHP内存不够用. 所以, 他问, 在执行下面的代码遍历返回的MySQL结果之前, 数据是否已经在内存中了? - 以下是代码片段: 以下是代码片段: while ($row = mysql_fetch_assoc($result)) { // ... } 当然, 这种问题有许多优化的方法. 不过, 就这个问题来讲, 我首先想到, MySQL是经典的C/S(Client/Server, 客户端/服务器)模型, 在遍历结果集之前, 底层的实现可能已经把所有的数据通过网络(假设使用TCP/IP)读到了Client的缓冲区, 也有另一种可能, 就是数据还在Server端的发送缓冲区里, 并没有传给Client. 在查看PHP和MySQL的源码之前, 我注意到PHP手册里有两个功能相近的函数: 以下是代码片段: 以下是代码片段:mysql_query() mysql_unbuffered_query() 两个函数的字面意思和说明证实了我的想法, 前一个函数执行时, 会把所有的结果集从Server端读到Client端的缓冲区中, 而后一个则没有, 这就是”unbuffered(未缓冲)”的意思. 那就是说, 如果用mysql_unbuffered_query()执行了一条返回大量结果集的SQL语句, 在遍历结果之前, PHP的内存是没有被结果集占用的. 而用mysql_query()来执行同样的语句的话, 函数返回时, PHP的内存占用便会急剧增加, 立即耗光内存. 如果阅读PHP的相关代码, 可以看到这两个函数的实现上的异同: 以下是代码片段:/* {{{ proto resource mysql_query(string query [, int link_identifier]) Sends an SQL query to MySQL */ PHP_FUNCTION(mysql_query) { php_mysql_do_query(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQL_STORE_RESULT); } /* }}} */ /* {{{ proto resource mysql_unbuffered_query(string query [, int link_identifier]) Sends an SQL query to MySQL, without fetching and buffering the result rows */ PHP_FUNCTION(mysql_unbuffered_query) { php_mysql_do_query(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQL_USE_RESULT); } /* }}} */ mysql_query() mysql_unbuffered_query() 两个函数的字面意思和说明证实了我的想法, 前一个函数执行时, 会把所有的结果集从Server端读到Client端的缓冲区中, 而后一个则没有, 这就是”unbuffered(未缓冲)”的意思. 那就是说, 如果用mysql_unbuffered_query()执行了一条返回大量结果集的SQL语句, 在遍历结果之前, PHP的内存是没有被结果集占用的. 而用mysql_query()来执行同样的语句的话, 函数返回时, PHP的内存占用便会急剧增加, 立即耗光内存. 如果阅读PHP的相关代码, 可以看到这两个函数的实现上的异同: 以下是代码片段: 以下是代码片段:/* {{{ proto resource mysql_query(string query [, int link_identifier]) Sends an SQL query to MySQL */ PHP_FUNCTION(mysql_query) { php_mysql_do_query(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQL_STORE_RESULT); } /* }}} */ /* {{{ proto resource mysql_unbuffered_query(string query [, int link_identifier]) Sends an SQL query to MySQL, without fetching and buffering the result rows */ PHP_FUNCTION(mysql_unbuffered_query) { php_mysql_do_query(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQL_USE_RESULT); } /* }}} */ 两个函数都调用了php_mysql_do_query(), 只差了第2个参数的不同, MYSQL_STORE_RESULT和MYSQL_USE_RESULT. 再看php_mysql_do_query()的实现: 以下是代码片段: 以下是代码片段:if(use_store == MYSQL_USE_RESULT) { mysql_result=mysql_use_result(&mysql->conn); } else { mysql_result=mysql_store_result(&mysql->conn); } if(use_store == MYSQL_USE_RESULT) { mysql_result=mysql_use_result(&mysql->conn); } else { mysql_result=mysql_store_result(&mysql->conn); } mysql_use_result()和mysql_store_result()是MySQL的C API函数, 这两个C API函数的区别就是后者把结果集从MySQL Server端全部读取到了Client端, 前者只是读取了结果集的元信息. 回到PHP, 使用mysql_unbuffered_query(), 可以避免内存的立即占用. 如果在遍历的过程不对结果进行”PHP缓存”(如放到某数组中), 则整个执行过程虽然操作了十万条或者百万条或者更多的数据, 但PHP占用的内存始终是非常小的.
核心提示:PHP基本上就是一种数组语言。时常要进行大量的数组循环操作,主要有两种方式,一种是foreach,另一种是while,到底哪种好哪种坏一直有争论,虽然我很早就意识到了这个问题,但是一直没有细究,懵懂的感觉一直持续到现在,为了以后能节省点CPU时间.....PHP基本上就是一种数组语言。时常要进行大量的数组循环操作,主要有两种方式,一种是foreach,另一种是while,到底哪种好哪种坏一直有争论,虽然我很早就意识到了这个问题,但是一直没有细究,懵懂的感觉一直持续到现在,为了以后能节省点CPU时间,下面总结一下:在循环里进行的是数组“读”操作,则foreach比while快:无格式查看复制到剪贴板打印代码?foreach ($array as $value) {echo $value;}while (list($key) = each($array)) {echo $array[$key];}foreach ($array as $value) {echo $value;}while (list($key) = each($array)) {echo $array[$key];}在循环里进行的是数组“写”操作,则while比foreach快:无格式查看复制到剪贴板打印代码?foreach ($array as $key => $value) {echo $array[$key] = $value . '...';}while (list($key) = each($array)) {$array[$key] = $array[$key] . '...';}foreach ($array as $key => $value) {echo $array[$key] = $value . '...';}while (list($key) = each($array)) {$array[$key] = $array[$key] . '...';}总结:通常认为,foreach涉及到值复制,一定会比while慢,但实际上,如果仅仅是在循环里进行数组的读操作,那么foreach是很快的,这是因为PHP采用的复制机制是“引用复制,写时拷贝”,这样看来,foreach的高效读操作就不难理解了。另外,既然foreach不适合处理数组写操作,那么我们可以得出一个结论,多数情况下,类似foreach ($array as $key => $value)形式的代码都应该被替换成while (list($key) = each($array))。这些技巧产生的速度差异在小项目里可能并不明显,但是在类似框架这样的大项目中,一次请求动辄便会涉及到几百几千几万次数组循环操作,差异就会明显放大。
首先让我们看一个问题: 如下代码的输出,var_dump(memory_get_usage());ええええええええええええええええええええええええ