PHP 7でメモリ内の大きなページを使用する

ページネーションは、ユーザープロセスに割り当てられたメモリを管理する方法です。 プロセスのメモリへのアクセスはすべて仮想であり、それらのアドレスの物理メモリのアドレスへの変換は、OSおよびハードウェアMMUによって実行されます。



ページ分割すると、メモリは固定サイズのブロックに分割されます。 Linux on x86 / 64プラットフォームでは、ページサイズは通常4 KBです。 各プロセスには、ページアドレスと物理メモリの対応に関する情報が保存されているテーブルが含まれています。これは、ページテーブルエントリの要素です。 メモリがアクセスされるたびにOSがこのテーブルにクロールしないように(そうでない場合は、各メモリアクセス要求を処理するために2回アクセスする必要があります)、 連想変換用の小さなキャッシュバッファー (Translationlookasideバッファー、TLB)が使用されます。 このハードウェアコンポーネントはMMUにあり、非常に高速で効率的です。 システムはTLBをスキャンして、ページアドレスと物理メモリの対応の記録を見つけます。 必要なレコードが存在しない場合、OSカーネルはメモリにアクセスし、目的の一致を探し、TLBの情報を更新して、メモリから必要なデータを取得する必要があります。



仮想メモリ管理の詳細については、 この資料をご覧ください。 それまでの間、PHP 7が大きなページ(巨大ページ)でどのように機能するかを見てみましょう。



なぜ大きなページが必要なのですか?



簡単です。ページが大きいほど、より多くのデータを入れることができます。 これは、OSカーネルが1回のメモリアクセスで大量のデータにアクセスできることを意味します。 各レコードがより多くのデータを「カバー」するようになったため、TLBでのミスの可能性も減少します。 バージョン2.6.20から、Linuxカーネルはラージページ(詳細はonetwothree )で動作するようになりました。 通常、大きなページは標準の512倍、4 KBではなく2 MBです。 ほとんどの場合、カーネルはいわゆる透過的な巨大ページマッピングを実行します。仮想メモリは標準の4 KBページに分割されますが、連続するページのグループが1つの大きなページに結合されることもあります。 これは通常、巨大なアドレス空間配列を操作するときに使用されます。 ただし、注意してください:このメモリは、オペレーティングシステムに小さな部分で返される可能性があり、これにより巨大なページサイズが失われ、カーネルはマージ手順をロールバックし、4 KBの512ページを再度割り当てる必要があります。



ユーザープロセス自体がマージ手順を開始できます。 ラージページ全体にデータを入力できると確信している場合は、カーネルに割り当てを問い合わせることをお勧めします。 カーネルはページテーブルのより少ない要素を表示する必要があるため、大きなページが存在するとメモリ管理が容易になります。 さらに、TLBのエントリ数が削減され、システム全体がより効率的かつ高速に動作します。



あなたを助けるOPCache



PHP 7で作業している間、メモリをより効率的に処理するために多くのエネルギーを費やしました。 PHP 7の重要な内部構造は、CPUキャッシュをより有効に使用するために書き直されました。 特に、空間的な局所性が改善されたため、より単純に接続されたデータがキャッシュに配置され、エンジンがメモリにアクセスする頻度が少なくなります。 OPCache拡張には、大きなページを操作するための機能が追加されました。



ラージページリクエスト



Unixの世界では、仮想メモリの割り当てを操作するための2つのAPIがあります。 mmap()関数を使用することをお勧めします。これにより、大きなページを選択できるようになります。 madvise()関数もあります。これは、メモリの一部をラージページに変換することに関するカーネルへのヒント(推奨)のみを提供しますが、保証はありません。



大きなページの割り当てを要求する前に、次のことを確認する必要があります。





sysctlを使用して、vm.nr_hugepagesを構成し、cat / proc / meminfoを使用してラージページの可用性を確認する必要があります。



> cat /proc/meminfo HugePages_Total: 20 HugePages_Free: 20 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB
      
      





この例では、それぞれ2 MBの20の大きなページが使用可能です。 Linux on x86 / 64プラットフォームは最大1 GBのページで動作しますが、大きなサイズのメリットがあるDBMSとは異なり、このサイズはPHPには推奨されません。



その後、APIを使用できます。 ラージページにメモリの一部を割り当てるには、アドレス空間の境界がラージページの境界と一致することを確認する必要があります。 いずれにせよ、これはCPUの効率を上げるために行われなければなりません。 その後、カーネルからページの選択を要求できます。 次の例では、Cを使用してアドレス調整が行われ、このタスクのバッファーはヒープから取得されます。 クロスプラットフォームの互換性のために、posix_memalign()などの既存の関数をアライメントに使用しません。



 #include <stdio.h> #include <sys/mman.h> #include <string.h> #include <stdlib.h> #define ALIGN 1024*1024*2 /* We assume huge pages are 2Mb */ #define SIZE 1024*1024*32 /* Let's allocate 32Mb */ int main(int argc, char *argv[]) { void *addr; void *buf = NULL; void *aligned_buf; /* As we're gonna align on 2Mb, we need to allocate 34Mb if we want to be sure we can use huge pages on 32Mb total */ buf = malloc(SIZE + ALIGN); if (!buf) { perror("Could not allocate memory"); exit(1); } printf("buf is at: %p\n", buf); */ Align on ALIGN boundary */ aligned_buf = (void *) ( ((unsigned long)buf + ALIGN - 1) & ~(ALIGN -1) ); printf("aligned buf: %p\n", aligned_buf); /* Turn the address to huge page backed address, using MAP_HUGETLB */ addr = mmap(aligned_buf, SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB | MAP_FIXED, -1, 0); if (addr == MAP_FAILED) { printf("failed mapping, check address or huge page support\n"); exit(0); } printf("mmapped: %p with huge page usage\n", addr); return 0; }
      
      





Cに精通している場合、説明することはあまりありません。 アプリケーションは終了するため、メモリは明示的に解放されません。この例は、アイデアを説明するためにのみ必要です。



プロセスがメモリをマークアップし、ほぼ終了すると、カーネルによって予約された非常に大きなページを確認できます。



 HugePages_Total: 20 HugePages_Free: 20 HugePages_Rsvd: 16 HugePages_Surp: 0 Hugepagesize: 2048 kB
      
      





ページは、データを書き込むまで仮想メモリに保存されないため予約されています。 ここでは、16ページが予約済みとしてマークされています。 16 x 2 Mb = 32 Mb-このメモリ量を使用して、mmap()を使用して大きなページを作成できます。



PHP 7コードセグメントを大きなページに配置する



PHP 7のコードセグメントのサイズは非常に大きいです。 LP64 x86 / 64マシンでは、約9 MB(デバッグビルド)です。



 > cat /proc/8435/maps 00400000-00db8000 r-xp 00000000 08:01 4196579 /home/julien.pauli/php70/nzts/bin/php /* text segment */ 00fb8000-01056000 rw-p 009b8000 08:01 4196579 /home/julien.pauli/php70/nzts/bin/php 01056000-01073000 rw-p 00000000 00:00 0 02bd0000-02ce8000 rw-p 00000000 00:00 0 [heap] ... ... ...
      
      





この例では、テキストセグメントは00400000から00db8000までのメモリを占有します。 つまり、バイナリPHPマシンコードの合計量は9 MBを超えます。 はい、PHPは進化し、機能が大きくなりすぎ、マシンコードに変換されるCコードが増えています。



メモリセグメントのプロパティを検討します。 従来の4kページを使用して強調表示されます。



 > cat /proc/8435/smaps 00400000-00db8000 r-xp 00000000 08:01 4196579 /home/julien.pauli/php70/nzts/bin/php Size: 9952 kB /* VM size */ Rss: 1276 kB /* PM busy load */ Pss: 1276 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 1276 kB Private_Dirty: 0 kB Referenced: 1276 kB Anonymous: 0 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 4 kB /* page size is 4Kb */ MMUPageSize: 4 kB Locked: 0 kB
      
      





カーネルは、このセグメントに大きなページの透過的な選択を使用しませんでした。 おそらくプロセスはpid8435でさらに使用されるため、おそらくこれに頼るでしょう。 ラージページのコア管理の問題については説明しませんが、OPCacheを使用すると、セグメントをラージページに再配布できます。



コードセグメントのサイズは変わらず、プロセスの終了時に移動しないため、この場合は大きなページを使用することをお勧めします。 9 952 Kbは2 Mbの4ページに収まり、残りは4 Kbの通常のページに分散します。



大規模なコードページセグメンテーション



次の場合:





 static void accel_move_code_to_huge_pages(void) { FILE *f; long unsigned int huge_page_size = 2 * 1024 * 1024; f = fopen("/proc/self/maps", "r"); if (f) { long unsigned int start, end, offset, inode; char perm[5], dev[6], name[MAXPATHLEN]; int ret; ret = fscanf(f, "%lx-%lx %4s %lx %5s %ld %s\n", &start, &end, perm, &offset, dev, &inode, name); if (ret == 7 && perm[0] == 'r' && perm[1] == '-' && perm[2] == 'x' && name[0] == '/') { long unsigned int seg_start = ZEND_MM_ALIGNED_SIZE_EX(start, huge_page_size); long unsigned int seg_end = (end & ~(huge_page_size-1L)); if (seg_end > seg_start) { zend_accel_error(ACCEL_LOG_DEBUG, "remap to huge page %lx-%lx %s \n", seg_start, seg_end, name); accel_remap_huge_pages((void*)seg_start, seg_end - seg_start, name, offset + seg_start - start); } } fclose(f); } }
      
      





OPCacheは、/ proc / self / mapsを開き、メモリのコードセグメントを検索します。 カーネルの依存関係を明示的に使用しないと、このような情報へのアクセスを取得できないため、別の方法で行うことはできません。 現在、procfsはすべてのUnixシステムで使用されています。



ファイルをスキャンし、コードセグメントを見つけ、ラージページのアドレス空間に従って境界を揃えます。 次に、境界線を揃えてaccel_remap_huge_pages()を呼び出します。



 # if defined(MAP_HUGETLB) || defined(MADV_HUGEPAGE) static int accel_remap_huge_pages(void *start, size_t size, const char *name, size_t offset) { void *ret = MAP_FAILED; void *mem; mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == MAP_FAILED) { return -1; } memcpy(mem, start, size); # ifdef MAP_HUGETLB ret = mmap(start, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_HUGETLB, -1, 0); # endif # ifdef MADV_HUGEPAGE if (ret == MAP_FAILED) { ret = mmap(start, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); if (-1 == madvise(start, size, MADV_HUGEPAGE)) { munmap(mem, size); return -1; } } # endif if (ret == start) { memcpy(start, mem, size); mprotect(start, size, PROT_READ | PROT_EXEC); } munmap(mem, size); return (ret == start) ? 0 : -1; } #endif
      
      





すべてが非常に簡単です。 新しい一時バッファー(mem)を作成し、そこにデータをコピーしてから、mmap()を使用して、アライメントされたバッファーを大きなページに分散しようとしました。 試行が失敗した場合、madvise()を使用してカーネルに通知できます。 セグメントがページ全体に分散されたら、データをコピーして戻します。



 00400000-00c00000 r-xp 00000000 00:0b 1008956 /anon_hugepage Size: 8192 kB Rss: 0 kB Pss: 0 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 0 kB Referenced: 0 kB Anonymous: 0 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 2048 kB MMUPageSize: 2048 kB Locked: 0 kB 00c00000-00db8000 r-xp 00800000 08:01 4196579 /home/julien.pauli/php70/nzts/bin/php Size: 1760 kB Rss: 224 kB Pss: 224 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 224 kB Private_Dirty: 0 kB Referenced: 224 kB Anonymous: 0 kB AnonHugePages: 0 kB Swap: 0 kB KernelPageSize: 4 kB MMUPageSize: 4 kB Locked: 0 kB
      
      





8 MBは4つの大きなページに分散され、標準では1760 KBです。 これにより、高負荷下でZendのパフォーマンスが3%向上しました。



大きなページを使用する場合:





おわりに



PHP 7のOPCache拡張機能が、「ラージページ」と呼ばれる現在広く普及しているメモリ管理技術を使用して、システムパフォーマンスを向上させる方法が明らかになりました。



ところで、いくつかのDBMS( OraclePostgreSQLなど )は、数年前から大きなページを利用しています。



All Articles