Skip to content

Memory leak in Ds\Set and Ds\Map when using Ds\Hashable as a key #216

@WebOtaku

Description

@WebOtaku

Hello everyone. The problem arises when we place an object belonging to a class implementing the Ds\Hashable interface into a Ds\Set or when we use it as a key in a Ds\Map.

Example:

<?php

class HashableObj implements \Ds\Hashable // No memory leak if not implements \Ds\Hashable
{
    public static int $countDestroyed = 0;

    public function __construct(public int $id, public string $name)
    {
    }

    public function __destruct()
    {
        ++self::$countDestroyed;
    }

    public function hash(): string
    {
        // return md5($this->id . '|' . $this->name);   // More memory leaked with bigger hashes
        // return crc32($this->id . '|' . $this->name);
        // return $this->id & 0x0f;                     // Less leaks with small hashes
        return $this->id;
    }

    public function equals($obj): bool
    {
        if (!$obj instanceof \app\commands\HashableObj) {
            return false;
        }

        return $this->id === $obj->id && $this->name === $obj->name;
    }
}

$baseMemory = memory_get_usage(true);
echo "Memory usage (start): " . (int)($baseMemory / 1048576) . " MB" . PHP_EOL;

$objectsCreated = 0;

for ($i = 0; $i < 25; $i++) {
    srand((new DateTimeImmutable())->getTimestamp());

    $map = new \Ds\Map();
    $map->allocate(50000);

    for ($j = 0; $j < 50000; $j++) {
        ++$objectsCreated;
        $map->put(new HashableObj(rand(1, 50000), "test_$j"), $j);
        // $map->put($j, new HashableObj(rand(1, 50000), "test_$j")); // No memory leak
    }

    $map->clear();

    echo "Memory usage: " . (int)((memory_get_usage(true) - $baseMemory) / 1048576) . " MB" . PHP_EOL;
    echo "Memory peak usage: " . (int)(memory_get_peak_usage(true) / 1048576) . " MB" . PHP_EOL;
    echo "$objectsCreated objects created" . PHP_EOL;
    echo HashableObj::$countDestroyed . " objects destroyed" . PHP_EOL;
}

echo "Memory usage (end): " . (int)((memory_get_usage(true) - $baseMemory) / 1048576) . " MB" . PHP_EOL;

Result:

Memory usage (start): 2 MB
Memory usage: 8 MB
Memory peak usage: 12 MB
50000 objects created
50000 objects destroyed
Memory usage: 10 MB
Memory peak usage: 14 MB
100000 objects created
100000 objects destroyed
Memory usage: 10 MB
Memory peak usage: 14 MB
150000 objects created
150000 objects destroyed
Memory usage: 12 MB
Memory peak usage: 16 MB
200000 objects created
200000 objects destroyed
Memory usage: 14 MB
Memory peak usage: 18 MB
250000 objects created
250000 objects destroyed
Memory usage: 16 MB
Memory peak usage: 20 MB
300000 objects created
300000 objects destroyed
Memory usage: 16 MB
Memory peak usage: 20 MB
350000 objects created
350000 objects destroyed
Memory usage: 18 MB
Memory peak usage: 22 MB
400000 objects created
400000 objects destroyed
Memory usage: 20 MB
Memory peak usage: 24 MB
450000 objects created
450000 objects destroyed
Memory usage: 22 MB
Memory peak usage: 26 MB
500000 objects created
500000 objects destroyed
Memory usage: 22 MB
Memory peak usage: 26 MB
550000 objects created
550000 objects destroyed
Memory usage: 24 MB
Memory peak usage: 28 MB
600000 objects created
600000 objects destroyed
Memory usage: 26 MB
Memory peak usage: 30 MB
650000 objects created
650000 objects destroyed
Memory usage: 28 MB
Memory peak usage: 32 MB
700000 objects created
700000 objects destroyed
Memory usage: 28 MB
Memory peak usage: 32 MB
750000 objects created
750000 objects destroyed
Memory usage: 30 MB
Memory peak usage: 34 MB
800000 objects created
800000 objects destroyed
Memory usage: 32 MB
Memory peak usage: 36 MB
850000 objects created
850000 objects destroyed
Memory usage: 34 MB
Memory peak usage: 38 MB
900000 objects created
900000 objects destroyed
Memory usage: 36 MB
Memory peak usage: 40 MB
950000 objects created
950000 objects destroyed
Memory usage: 36 MB
Memory peak usage: 40 MB
1000000 objects created
1000000 objects destroyed
Memory usage: 38 MB
Memory peak usage: 42 MB
1050000 objects created
1050000 objects destroyed
Memory usage: 40 MB
Memory peak usage: 44 MB
1100000 objects created
1100000 objects destroyed
Memory usage: 42 MB
Memory peak usage: 46 MB
1150000 objects created
1150000 objects destroyed
Memory usage: 42 MB
Memory peak usage: 46 MB
1200000 objects created
1200000 objects destroyed
Memory usage: 44 MB
Memory peak usage: 48 MB
1250000 objects created
1250000 objects destroyed
Memory usage (end): 44 MB

Environment:

PHP 8.1.28 (cli) (built: Apr 22 2024 09:45:11) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.28, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.28, Copyright (c), by Zend Technologies
    with Xdebug v3.3.2, Copyright (c) 2002-2024, by Derick Rethans
DS extension ver.: 1.6.0
[PHP Modules]
bcmath
bz2
calendar
Core
ctype
curl
date
dba
dom
ds
enchant
exif
FFI
fileinfo
filter
ftp
gd
gettext
gmp
grpc
hash
iconv
igbinary
imagick
imap
intl
json
ldap
libxml
mbstring
memcached
msgpack
mysqli
mysqlnd
odbc
openssl
pcntl
pcre
PDO
pdo_dblib
pdo_mysql
PDO_ODBC
pdo_pgsql
pdo_sqlite
pgsql
Phar
posix
pspell
readline
redis
Reflection
session
shmop
SimpleXML
snmp
soap
sockets
sodium
SPL
sqlite3
standard
sysvmsg
sysvsem
sysvshm
tidy
tokenizer
xdebug
xml
xmlreader
xmlrpc
xmlwriter
xsl
Zend OPcache
zip
zlib

[Zend Modules]
Xdebug
Zend OPcache

This problem was also observed on PHP 8.3 with the DS extension version 1.5.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions