Skip to content

Instantly share code, notes, and snippets.

@SerafimArts
Last active April 14, 2023 11:20
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SerafimArts/dcf64d3211831d1c02a403c104d97f19 to your computer and use it in GitHub Desktop.
Save SerafimArts/dcf64d3211831d1c02a403c104d97f19 to your computer and use it in GitHub Desktop.
PHP Memory Map Visualization
/**
* -----------------------------------------------------------------------------
* zend_long.h
* -----------------------------------------------------------------------------
*/
#ifdef ZEND_ENABLE_ZVAL_LONG64
typedef int64_t zend_long;
typedef uint64_t zend_ulong;
typedef int64_t zend_off_t;
#define ZEND_LONG_MAX INT64_MAX
#define ZEND_LONG_MIN INT64_MIN
#define ZEND_ULONG_MAX UINT64_MAX
#define Z_L(i) INT64_C(i)
#define Z_UL(i) UINT64_C(i)
#define SIZEOF_ZEND_LONG 8
#else
typedef int32_t zend_long;
typedef uint32_t zend_ulong;
typedef int32_t zend_off_t;
#define ZEND_LONG_MAX INT32_MAX
#define ZEND_LONG_MIN INT32_MIN
#define ZEND_ULONG_MAX UINT32_MAX
#define Z_L(i) INT32_C(i)
#define Z_UL(i) UINT32_C(i)
#define SIZEOF_ZEND_LONG 4
#endif
/**
* -----------------------------------------------------------------------------
* zend_alloc_sizes.h
* -----------------------------------------------------------------------------
*/
// 22:
#define ZEND_MM_CHUNK_SIZE ((size_t) (2 * 1024 * 1024)) /* 2 MB */
#define ZEND_MM_PAGE_SIZE (4 * 1024) /* 4 KB */
#define ZEND_MM_PAGES (ZEND_MM_CHUNK_SIZE / ZEND_MM_PAGE_SIZE) /* 512 */
#define ZEND_MM_FIRST_PAGE (1)
#define ZEND_MM_MIN_SMALL_SIZE 8
#define ZEND_MM_MAX_SMALL_SIZE 3072
#define ZEND_MM_MAX_LARGE_SIZE (ZEND_MM_CHUNK_SIZE - (ZEND_MM_PAGE_SIZE * ZEND_MM_FIRST_PAGE))
/**
* -----------------------------------------------------------------------------
* zend_alloc.h
* -----------------------------------------------------------------------------
*/
// 243:
typedef struct _zend_mm_heap zend_mm_heap;
// 266:
ZEND_API zend_mm_heap *zend_mm_get_heap(void);
/**
* -----------------------------------------------------------------------------
* zend_alloc.c
* -----------------------------------------------------------------------------
*/
// 150:
typedef uint32_t zend_mm_page_info; /* 4-byte integer */
typedef zend_ulong zend_mm_bitset; /* 4-byte or 8-byte integer */
// 160:
#define ZEND_MM_BITSET_LEN (sizeof(zend_mm_bitset) * 8) /* 32 or 64 */
#define ZEND_MM_PAGE_MAP_LEN (ZEND_MM_PAGES / ZEND_MM_BITSET_LEN) /* 16 or 8 */
typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */
// 192:
#define ZEND_MM_BINS 30
// 197:
typedef struct _zend_mm_chunk zend_mm_chunk;
// 234:
struct _zend_mm_heap {
#if ZEND_MM_CUSTOM
int use_custom_heap;
#endif
#if ZEND_MM_STORAGE
zend_mm_storage *storage;
#endif
#if ZEND_MM_STAT
size_t size; /* current memory usage */
size_t peak; /* peak memory usage */
#endif
zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
size_t real_size; /* current size of allocated pages */
#endif
#if ZEND_MM_STAT
size_t real_peak; /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
size_t limit; /* memory limit */
int overflow; /* memory overflow flag */
#endif
zend_mm_huge_list *huge_list; /* list of huge allocated blocks */
zend_mm_chunk *main_chunk;
zend_mm_chunk *cached_chunks; /* list of unused chunks */
int chunks_count; /* number of allocated chunks */
int peak_chunks_count; /* peak number of allocated chunks for current request */
int cached_chunks_count; /* number of cached chunks */
double avg_chunks_count; /* average number of chunks allocated per request */
int last_chunks_delete_boundary; /* number of chunks after last deletion */
int last_chunks_delete_count; /* number of deletion over the last boundary */
#if ZEND_MM_CUSTOM
union {
struct {
// void *(*_malloc)(size_t);
// void (*_free)(void*);
// void *(*_realloc)(void*, size_t);
void* _malloc;
void *_free;
void *_realloc;
} std;
struct {
// void *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
// void (*_free)(void* ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
// void *(*_realloc)(void*, size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
void* _malloc;
void *_free;
void *_realloc;
} debug;
} custom_heap;
// HashTable *tracked_allocs;
void *tracked_allocs;
#endif
};
// 284:
struct _zend_mm_chunk {
zend_mm_heap *heap;
zend_mm_chunk *next;
zend_mm_chunk *prev;
uint32_t free_pages; /* number of free pages */
uint32_t free_tail; /* number of free pages at the end of chunk */
uint32_t num;
char reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)];
zend_mm_heap heap_slot; /* used only in main chunk */
zend_mm_page_map free_map; /* 512 bits or 64 bytes */
zend_mm_page_info map[ZEND_MM_PAGES]; /* 2 KB = 512 * 4 */
};
{
"require": {
"php": "^8.2",
"ext-ffi": "*",
"ffi/proxy": "^1.0",
"ffi/preprocessor": "^0.2"
},
"autoload": {
"psr-4": {
"Serafim\\MemProf\\": "src"
}
}
}
<?php
declare(strict_types=1);
namespace Serafim\MemProf\Zend;
use FFI\Contracts\Preprocessor\Exception\DirectiveDefinitionExceptionInterface;
use FFI\Contracts\Preprocessor\Exception\PreprocessorExceptionInterface;
use FFI\Contracts\Preprocessor\PreprocessorInterface;
use FFI\Preprocessor\Preprocessor;
final readonly class Headers implements \Stringable
{
/**
* @var non-empty-string
*/
private const HEADERS_PATHNAME = __DIR__ . '/../../resources/alloc.h';
/**
* @var non-empty-string
*/
private string $headers;
/**
* @throws PreprocessorExceptionInterface
*/
public function __construct(PreprocessorInterface $pre = null)
{
$pre = $this->prepare($pre ?? new Preprocessor());
$this->headers = (string)$pre->process(
new \SplFileInfo(self::HEADERS_PATHNAME),
);
}
/**
* @param PreprocessorInterface $pre
*
* @return PreprocessorInterface
* @throws DirectiveDefinitionExceptionInterface
*/
private function prepare(PreprocessorInterface $pre): PreprocessorInterface
{
$pre = clone $pre;
if (\PHP_INT_SIZE === 8) {
$pre->define('ZEND_ENABLE_ZVAL_LONG64', '1');
}
$pre->define('ZEND_MM_STAT', '1');
$pre->define('ZEND_MM_STORAGE', '1');
$pre->define('ZEND_MM_CUSTOM', '1');
$pre->define('ZEND_MM_LIMIT', '1');
if (\PHP_OS_FAMILY === 'Windows') {
$pre->define('ZEND_API', '__declspec(dllimport)');
$pre->define('ZEND_FASTCALL', '__vectorcall');
} else {
$pre->define('ZEND_API', '');
}
$pre->define('zend_mm_storage', 'void');
$pre->define('zend_mm_free_slot', 'void');
$pre->define('zend_mm_huge_list', 'void');
return $pre;
}
public function __toString(): string
{
return $this->headers;
}
}
<?php
declare(strict_types=1);
namespace Serafim\MemProf\Zend;
use FFI\CData;
use FFI\Proxy\Proxy;
/**
* @method CData|ZendMMHeap zend_mm_get_heap()
*/
final class Library extends Proxy
{
public function __construct(
string|\Stringable $headers = new Headers(),
string $library = null,
) {
$ffi = \FFI::cdef((string)$headers, $library ?? $this->getLibrary());
parent::__construct($ffi);
}
/**
* @return non-empty-string
*/
private function getLibrary(): string
{
if (\PHP_OS_FAMILY === 'Windows') {
return 'php' . \PHP_MAJOR_VERSION . '.dll';
}
return '';
}
}
<?php
declare(strict_types=1);
namespace Serafim\MemProf;
use Serafim\MemProf\Page\Type;
use Serafim\MemProf\Zend\Library;
/**
* @template-implements \IteratorAggregate<int, Page>
*/
final class Map implements \IteratorAggregate
{
// zend_alloc.c:165
private const ZEND_MM_IS_FRUN = 0x00000000;
private const ZEND_MM_IS_LRUN = 0x40000000;
private const ZEND_MM_IS_SRUN = 0x80000000;
// zend_alloc.c:169
private const ZEND_MM_LRUN_PAGES_MASK = 0x000003ff;
private const ZEND_MM_LRUN_PAGES_OFFSET = 0;
// zend_alloc.c:172
private const ZEND_MM_SRUN_BIN_NUM_MASK = 0x0000001f;
private const ZEND_MM_SRUN_BIN_NUM_OFFSET = 0;
// zend_alloc.c:175
private const ZEND_MM_SRUN_FREE_COUNTER_MASK = 0x01ff0000;
private const ZEND_MM_SRUN_FREE_COUNTER_OFFSET = 16;
// zend_alloc.c:178
private const ZEND_MM_NRUN_OFFSET_MASK = 0x01ff0000;
private const ZEND_MM_NRUN_OFFSET_OFFSET = 16;
// zend_alloc_sizes.h:32
private const ZEND_MM_BINS_INFO_COUNT = [
1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 5, 3,
1, 1, 5, 3, 2, 2,
5, 3, 7, 4, 5, 3,
];
/**
* @var array<int<0, max>, Page>
*/
private array $map;
/**
* @var object|\ArrayAccess<int<0, max>, int>
*/
private object $ref;
public function __construct(
private Library $zend,
) {
$heap = $this->zend->zend_mm_get_heap();
$this->ref = $heap->main_chunk->map;
for ($i = 0, $size = \count($this->ref); $i < $size; ++$i) {
$this->map[] = new Page($i === 0 ? Type::INIT : Type::FREE);
}
$this->update();
}
public function draw(): string
{
$result = '';
for ($i = 0; $i < 8; ++$i) {
for ($j = 0; $j < 64; ++$j) {
$page = $this->map[$i * 64 + $j];
if ($page->highlight) {
$result .= $page->type === Type::FREE ? "\033[33m" : "\033[31m";
}
$result .= match ($page->type) {
Type::FREE => '□',
Type::INIT => '◙',
Type::SMALL => '▣',
Type::LARGE => '▩',
};
if ($page->highlight) {
$result .= "\033[0m";
}
}
$result .= "\n";
}
return $result;
}
public function update(): void
{
$index = 0;
$length = \count($this->map);
while ($index < $length) {
$page = $this->ref[$index];
$count = 1;
if ($page === self::ZEND_MM_IS_FRUN) {
$this->updatePage($index, Type::FREE, $count);
$index += $count;
continue;
}
$isSmall = ($page & self::ZEND_MM_IS_SRUN) === self::ZEND_MM_IS_SRUN;
$isLarge = ($page & self::ZEND_MM_IS_LRUN) === self::ZEND_MM_IS_LRUN;
if ($isSmall && $isLarge) {
$count = self::ZEND_MM_BINS_INFO_COUNT[$page & self::ZEND_MM_SRUN_BIN_NUM_MASK];
$this->updatePage($index, Type::SMALL, $count);
} elseif ($isSmall) {
$this->updatePage($index, Type::SMALL, $count);
} elseif ($isLarge) {
$count = $page & self::ZEND_MM_SRUN_BIN_NUM_MASK;
$this->updatePage($index, Type::LARGE, $count);
}
$index += ($count ?: 1);
}
}
private function updatePage(int $id, Type $type, int $count): void
{
for ($length = $id + $count; $id < $length; ++$id) {
$this->map[$id]->highlight = $this->map[$id]->type !== $type;
$this->map[$id]->type = $type;
}
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->map);
}
}
<?php
declare(strict_types=1);
namespace Serafim\MemProf;
use Serafim\MemProf\Page\Type;
final class Page
{
public function __construct(
public Type $type,
public bool $highlight = false,
) {
}
}
<?php
declare(strict_types=1);
namespace Serafim\MemProf\Page;
enum Type
{
case FREE;
case INIT;
case SMALL;
case LARGE;
}
@SerafimArts
Copy link
Author

Result:

result

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment