ホームページ > バックエンド開発 > PHPチュートリアル > PHPにおける巨大データオブジェクトのメモリオーバーヘッドに関する研究

PHPにおける巨大データオブジェクトのメモリオーバーヘッドに関する研究

WBOY
リリース: 2016-06-23 14:27:51
オリジナル
930 人が閲覧しました

まず最初に、誤解しないでください。私はこの問題に関する研究結果を公開するつもりはありませんが、この問題の分析と研究に協力していただきたいと思います :)

問題の背景を簡単に説明します。 PHP の実装 Web サイトでは、すべてのプログラム ファイルの先頭に共通ファイル common.php が含まれています。現在、ビジネス上のニーズにより、 $huge_array = array(1,2,3,...,500000) のように、「巨大な」データ オブジェクト (約 500k の int 値を含む配列オブジェクト) が common.php で定義されています。システム全体で $huge_array への「読み取り」アクセスのみが存在します。システムは 100 の同時リクエストで安定して実行し続ける必要があると仮定します。

質問 1: $huge_array は common.php ソース ファイルで約 10M を占有し (当面は問題ありません)、メモリにロードされると 4M を占有する可能性があります (単なる推定です。そのサイズを正確に測定するには、この記事で説明する内容ではありません)。問題は、PHP 自体が HTTP リクエストを処理するたびに独立したプロセス (またはスレッド) を開始するため、この約 4M のメモリ ブロックをメモリに再ロードする必要があるということです。メモリを共有できない場合、同時に 400M 近くの物理メモリを占有する可能性があり、メモリ使用量とメモリ アクセス効率の点で非常に不利になります。

質問 2: 特定のキャッシュ メカニズム (APC、XCache など) が有効になっている場合、そのようなキャッシュ メカニズムにはオペコードをキャッシュする機能があることがわかっていますが、それはスクリプトのコンパイルの繰り返し作業を減らすだけのようです。実行時に変数をロードするための共有メモリの役割も果たせるでしょうか?結局のところ、これらの多くの int 値はオペコードの一部としても存在する必要があるため、それが特定の役割を果たすことを願っています。

質問 3: 演算コードをキャッシュするための上記の XCache の使用が目的を達成できない場合、XCache を直接操作すると効果的ですか?つまり、$huge_array は common.php に記述されるのではなく、キャッシュ エンジンに記述されます。

この記事の目的は、分析と調査を通じて、この使用法がメモリ使用量のボトルネックを引き起こすかどうかを判断し、問題がある場合はそれを最適化する方法を決定することです。もちろん、巨大なデータ オブジェクト自体のサイズを削減するためにあらゆる手段を試すことが最初に考慮すべき最も重要なことですが、データ構造とアルゴリズムについてはこの記事では説明しません。コードを書く時間がない場合は、ソリューションが合理的である限り、この問題に関する独自の分析や見解を表明することを歓迎します。 、テストコードを書いてみたいと思います^_^


??????????????????????????????? ?
CSDN フォーラムが提供するプラグイン拡張機能をベースに、署名ファイル ツールを作成し、皆さんと共有しました。技術的な交流は大歓迎です :)


ディスカッション (解決策) への返信

以来500k を超えるデータですが、インデックスを作成してセグメントに分けて処理する必要があるのでしょうか? なぜすべてをロードする必要があるのでしょうか? ? ?

1. はい、メモリに再ロードする必要があります
2. すべてのプログラム ファイルにこれが含まれているため、オペコードの一部でもあるため、キャッシュする必要があります
3. それは非常に退屈なことですが、その方が良いですデータ構造とアルゴリズムを最適化するためです

1. 上司がそう言いました
2. これはオペコードの一部なので、もちろん最適化する必要があります
3. コンパイル時間がインデックスによってデータを取得する時間よりも長い場合、なぜコンパイルするのか?キーに応じて対応するデータを取得するためだけにコンパイルするのに、キーに応じてデータを取得する方法があるのに、なぜ PHP の配列データ構造を使用する必要があるのでしょうか。この質問は、データを txt ファイルから取得すべきか、それとも含まれている PHP から取得すべきか、というようなものです。 たとえば、1。txtの内容は次のとおりです
1111
2222,
3333,
4444 ,

);

1.php の場合、各プロセスは $a を初期化してから $a[3] を取得する必要があります
1.txt の場合は、方法を最適化するだけです4(3 +1) をすばやく取得するには、この行の内容で十分です。すべてのプロセスを共有できます。

しかし、ここで問題があります。同時実行性の問題です。同時実行性の問題を合理的に最適化するにはどうすればよいでしょうか?

この問題を解決する一般的な方法はありません。時間のためのスペース (同時待機時間なしで 400M)、またはスペースのための時間 (4M、ただしデータ取得時に待機時間あり) のいずれかです。

ビッグデータを処理するには共有メモリを使用する必要があると思います。そうしないと、ビッグデータのためにすべての http プロセスでメモリ負荷が増加します。 Xcache と apc は両方ともオペコード キャッシュですよね?つまり、PHP が毎回字句解析を使用せず、オペコードの実行段階に直接移行する場合でも、重要な点は、http If リクエストごとにメモリが 1 回割り当てられるということです。この大きな配列を memcache に保存すると、リクエストはメモリの一部を共有します。書き込み操作がないため、memcache での同時読み取りが 100 件発生しても問題はありません。楽観的ロックをサポートします。

まず、これを memcache に置きます

必要な場所に取得します、それだけで十分です

この巨大なデータ ブロックをクライアントに置くことができます
それを行うには json メソッドを使用します
サービス File_Put_Contents("?.js を使用します) ",$value,LOCK_EX); .js ファイルを出力します。ファイル形式は次のとおりです:

var Class=[{"_p":[1,26,"??\/影?展示"],"_l":["20|超大?桌(11人以上)","21|12人大?桌","22|16人座大?桌","23|18人座大?桌","24|20人座大?桌","143|婚\/宴\/??","144|???","145|展示?","146|表演?","147|影?院","148|舞?","149|夜??","150|Disco Pub","151|MTV室","152|卡拉OK室","153|???",]},{"_p":[2,17,"新的分?1"],"_l":[""]},{"_p":[2,18,"新的分?2"],"_l":[""]},{"_p":[4,15,"交通工具"],"_l":[""]},];
ログイン後にコピー

クライアントがこのデータを使用する必要がある場合、jquery の
$.getScript("? .js",function) を使用できます。 (){});
これらの JSON データを読み取るだけです。これはネットワーク帯域幅を消費するだけで、サーバーのメモリ リソースを占有することはありません

1. はい、再度メモリに保存する必要があります
私もそう思います。 !

2. すべてのプログラム ファイルには が含まれているため、これもオペコードの一部であるため、キャッシュする必要があります。
多数の int 値を定数データとして含む配列の初期値は、オペコードの一部である必要があります。それはまだわかりませんが、プログラム内の変数に値を割り当てるために使用する場合、最初にそれをロードするためにメモリが割り当てられます。割り当てられていない場合、この変数はオペコードが格納されているアドレスを直接指していることになります。そうすると、オペコードが格納されているブロック メモリはプロセス間で共有されているのかという疑問が再び生じます。

3. それはとても退屈なことです。データ構造とアルゴリズムを最適化した方が良いです
私もそう思います!キャッシュエンジンを直接操作するというのは、結局のところ、変数に値を代入するだけであり、その可能性は以前の「オペコードを間接的にキャッシュしてメモリを共有する」よりもそれほど大きくはないようです。

2. これはオペコードの一部です。もちろん最適化する必要があります
はっきりとはわかりませんが、XCache を使用した後、メモリ使用量が最適化されたということですか?それとも、やはり最適化作業について自分で考える必要があるのでしょうか?

3. コンパイル時間がインデックスによってデータを取得する時間よりも長い場合、なぜコンパイルする必要があるのでしょうか?キーに応じて対応するデータを取得するためだけにコンパイルするのに、キーに応じてデータを取得する方法があるのに、なぜ PHP の配列データ構造を使用する必要があるのでしょうか。この質問は、データを txt ファイルから取得すべきか、それとも含まれている PHP から取得すべきか、というようなものです。
たとえば、1.txt の内容は次のとおりです
1111
2222
33...
おっしゃるとおり、これはまさに最適化のアイデアです。ただし、キー/値の数が膨大であるため、それらがすべて分割されると、それぞれのキー/値がキャッシュ内のエントリとして使用されることになり、一般的なキャッシュの使用法と一致していないようです(これは NoSQL に非常に似ています、ふふ) とにかく、これはこの投稿の議論の目的ではありません。当初の計画が本当に無駄であれば、今回のような最適化計画を検討するしかありません。

ビッグデータを処理するには共有メモリが必要だと思います。そうしないと、ビッグデータのためにすべての http プロセスでメモリ負荷が増加します。 Xcache と apc は両方ともオペコード キャッシュですよね?つまり、PHP が毎回字句解析を使用せず、オペコードの実行段階に直接移行する場合でも、大きな配列にメモリを割り当てる必要があるのは、HTTP リクエストごとにメモリが 1 回割り当てられるということです。まさにこれが問題なのです!

この大きな配列を memcache に保存すると、複数の http リクエストがメモリを共有することになります。書き込み操作がないため、memcache での同時読み取りが 100 回行われても問題はありません。楽観主義をサポートします。
「書き込み」操作はまったく必要ありません。「読み取り専用」で十分です。しかし問題は、この巨大なデータ オブジェクトを memcache に置いた後、キャッシュ エンジン内で 1 つのメモリしか占有しないにもかかわらず、それをどのように使用するかということです。常に変数に値を代入する必要がありますよね? Memcache は「ネットワーク キャッシュ」なので、PHP プロセスにデータを転送する必要があるため、メモリを新たに割り当てる必要があります。「共有メモリ方式でこの巨大なデータ オブジェクトにアクセスすることを期待している」という観点からは、まだ memcache に勝機があるかもしれません。 APC や XCache のようなインプロセス キャッシュ エンジンほど優れていません :)

那个包含大量 int 值的 array 初始值,作为常量数据,肯定是 opcode 的一部分。只是我还不太确定,当在程序中用它给一个变量赋值的时候,会不会先要分配一块内存来装载它。如果不分配的话,那就是要让这个变量直接指向存储 opcode 的那段地址,那么问题又来了,存储 opcode 的块内存是否是进程间共享的呢?

这个就不要想了,opcode仅仅是将语法编译的步骤给省略了,例如:
$a = 1 + 1;
替换成opcode以后就是
ZEND_ADD ~0 1 1
只不过~0代替了$a, 如果真正运行的话~0还是要单独分配空间的,否则,php怎么去获取数据呢?如果另外一个进程修改这个值,是否还要影响到另外一个进程呢?是不是就不能保证每个进程的逻辑正确性呢?因为每个进程都是相同的代码,我不知道我的$a还是否是刚初始化的状态?

所以opcode的缓存不会缓存数据的,仅仅是缓存另外一种代码(脚本)格式!具体的数据还是要在运行的时候重新分配内存的!

关于opcode更多可以点我

另外我说的那个例子(当然1.txt可以用任何工具替代,memcache、sql等),仅仅是你可以开发接口,例如读一个分配内存空间,如果要下一个内容还可以使用这点内存空间存取下一个内容,这样才能保证每个进程的内存空间的减少

伪代码示例:


while($data = get()) {
// dosamething
}

来起到
foreach($datas as $data){
}

通过加大获取一条数据的时间,来减小内存的消耗

很想加入这个话题,可惜个人水平有限,缓存什么的都不太懂,说不上什么


我从另一个角度去说说个人看法??

web编程是一定要考虑并发的,包括服务器连接,而不是单纯为了解决问题
这也是很多搞桌面开发的人没搞懂的地方,从而引发“php很差劲”之类的说法

php的优势就是快速解决简单问题,完成并扔给客户端,结束一个连接,腾空给下一个请求
复杂计算其实应该用更核心的语言配合更高质量的服务器去完成

一个复杂的计算消耗的资源对核心语言和高性能服务器来说都是值得的,因为主要的目的就在于计算得出结果
例如nasa的服务器和上面实用的程序,都要精益求精,可能计算错小数点亿万位都会造成“火星撞地球”的结果

但对信息传播网络来说损失却是巨大的,因为每消耗多一秒钟,你的信息就可能少传给一个甚至几十个人
消耗内存和消耗时间都是一样,内存使用太大,也会造成tcp连接的不稳定


所以,大的多的参数可能对程序的灵活度很好,适应更多的使用者,但对一般网络访问却是失败的选择
应该把这些参数切割,把用户群分类,每用户可选择更少的参数达到目的就够了
例如一个面向全球的网站,不是把所有语言都放在一起做参数程序自适应,而是由用户选择语言,只针对这种语言写代码

如果一些复杂的计算,应该选用其他语言或控件完成某些既定过程,缩减web程序的响应过程
#6说的是其中一种情况,当然这样未必合适你的需要,但他的提法是符合传播网站的业务逻辑的


说了那么多,估计也没能解决你的需求,能看完我都感到荣幸……

4M,咔咔,不算太大

把它简化成一个值, 然后测测你目前的运行的php的内存峰值比比看....

看看这段代码对你有没有帮助,无需载入文件到内存

<?php/*111222333444555*/$file = new SplFileObject(__FILE__);$file->seek(3);//这里是行数,从0开始echo $file->current().'<br>';?>
ログイン後にコピー


测试一个50M左右的xml,需时不到0.01秒??行数越大,需时越多

抱歉耽搁了这么久才来跟进这个帖子,这期间在工作之余补习了一下 opcode 相关的知识。

感谢 hnxxwyq 在 #10 楼的回帖,关于 opcode 的解释很有启发性,促使我去找了个用于分析 opcode 的小工具(请参考 VLD是个好东西),顺便把 build PHP under windows 也操练了一遍,hehe。

实践的结果是,对于这段程序:

<?php$huge_array = array(1,2,3);$elem = $huge_array['2'];?>
ログイン後にコピー

编译出来的 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. 上記のプログラムは、テストを容易にするために一緒に書かれています。私はそれを読んでいませんでしたが、テストでは外部ファイルを意味する、バイナリを使用したと言いました。しかし、問題はありません。バイナリの場合は平文で検索できないということですが、検索は全体として読み込む必要がありますが、SplFileObjectの全文検索は全体として読み込む必要はありません
3.それは私の考えではありません。spl を学習しているときに外国人がどのようにパスするかを議論しているのを見ました。1G SQL ファイルで使用されている解決策は、仏陀に捧げるために花を借りることです
SPL には本当に宝物がたくさんありました。事前に注意して勉強してます!

一度にすべてをメモリに入れたくないので、分類してファイルに入れます。検索する必要がある場合は、二分法で検索してください。


SPLの手法もおそらく同じはずです。

ご参加いただきました皆様、ありがとうございます。私の最初の質問は基本的に回答されたと思います。


#6 さん、申し訳ありませんが、CSDN の投稿インターフェースのロジックがわからないので、返信に対してポイントは与えません :(

拡張機能にコンパイルされます

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート