Валентин @vudaltsov
// PHP <8.4
$request = (new Psr7Request())->withMethod('GET')->withUri('/hello');
// PHP >=8.4
$request = new Psr7Request()->withMethod('GET')->withUri('/hello');
http:
address: 0.0.0.0:80
pool:
debug: false
num_workers: 20
supervisor:
# выставляй ~80-90% от memory_limit
max_worker_memory: 100
$a = 'PHP';
$b = $a;
$c = $a;
xdebug_debug_zval('a'); // refcount=3, is_ref=0
$b = 42;
xdebug_debug_zval('a'); // refcount=2, is_ref=0
unset($c);
xdebug_debug_zval('a'); // refcount=1, is_ref=0
// ...
private function getRuleSetSize(/** ... */)
{
$solver = new Solver($policy, $pool, $installedRepo, $this->io);
try {
$solver->solve($request, $this->ignorePlatformReqs);
return $solver->getRuleSetSize();
} catch (SolverProblemsException $e) {
// ...
}
}
final class Foo
{
public string $string = 'GC TEST!!!';
public self $self;
}
for ($i = 0; $i <= 10_000_000; ++$i) {
$object = new Foo();
$object->self = $object;
}
echo memory_get_peak_usage() / 1024 / 1024, PHP_EOL;
time php -dzend.enable_gc=0 -dmemory_limit=-1 gc-test.php
Memory: 891.31MiB
Time: 0.47s
time php -dzend.enable_gc=1 -dmemory_limit=-1 gc-test.php
Memory: 1.26MiB
Time: 0.63s
final readonly class Foo
{
/**
* @var list<Closure>
*/
private array $callbacks;
public function __construct()
{
$this->callbacks = [
function ($stackPos) {
// ...
},
];
}
}
use PhpParser\Lexer;
use PhpParser\Parser\Php7;
gc_disable();
for ($i = 0; $i < 4; ++$i) {
new Php7(new Lexer());
echo memory_get_usage().PHP_EOL;
}
// 7992640
// 8279744
// 8558624
// 8853888
class Php8 extends ParserAbstract
{
// ...
protected function initReduceCallbacks(): void
{
$this->reduceCallbacks = [
1 => function ($stackPos) {
$this->semValue = $this->handleNamespaces(/** ... */);
},
// ...
];
}
}
class Php8 extends ParserAbstract
{
// ...
protected function initReduceCallbacks(): void
{
$this->reduceCallbacks = [
1 => static function ($self, $stackPos) {
$self->semValue = $self->handleNamespaces(/** ... */);
},
// ...
];
}
protected function doParse()
{
// ...
$callback($this, $stackPos);
// ...
}
}
final class PhpParserMemoryTest extends TestCase
{
#[RunInSeparateProcess]
public function testItIsGarbageCollected(): void
{
gc_disable();
$phpParser = (new ParserFactory())->createForHostVersion();
$weakReference = WeakReference::create($phpParser);
unset($phpParser);
self::assertNull($weakReference->get());
}
}
final readonly class A
{
public function __construct()
{
static fn (): self => $this;
}
}
Psalm output:
ERROR: InvalidScope - 5:31 - Invalid reference to $this in a static context
final class CsvParser
{
/**
* @return Generator<list<string>>
*/
public function parse(string $file): Generator
{
$handler = fopen($file, 'rb');
flock($handler, LOCK_SH);
while (false !== $row = fgetcsv($handler)) {
yield $row;
}
flock($handler, LOCK_UN);
fclose($handler);
}
}
final readonly class CursorReader
{
public function __construct(private PDO $postgres) {}
/** @return Generator<string> */
public function read(): Generator
{
$cursor = uniqid('cursor_');
$this->connection->exec(
"declare {$cursor} cursor for
select id from my_table",
);
do {
$batchSize = 0;
$result = $this->connection->query(
"fetch 1000 from {$cursor}"
);
foreach ($result as ['id' => $id]) {
++$batchSize;
yield $id;
}
} while ($batchSize === 1000);
}
}
$postgres = new PDO('...');
$reader = new CursorReader($postgres);
foreach ($reader->read() as $id) {
var_dump($id);
}
$postgres = new PDO('...');
$reader = new CursorReader($postgres);
$postgres->beginTransaction();
try {
foreach ($reader->read() as $id) {
var_dump($id);
}
$postgres->commit();
} catch (Throwable $exception) {
$postgres->rollback();
throw $exception;
}
insert into table_name (col_1, col_2)
values
(1, 1),
(1, 2),
(1, 3),
...
final readonly class EffectiveWriter
{
public function __construct(
private PDO $postgres,
) {}
/**
* @param iterable<array> $rows
*/
public function write(iterable $rows): void
{
$file = tempnam(sys_get_temp_dir(), 'EffectiveWriter');
$handle = fopen($file, 'wb');
foreach ($rows as $row) {
fwrite($handle, encodeRow($row));
}
$this->postgres->pgsqlCopyFromFile('my_table', $file);
fclose($handle);
unlink($file);
}
}
/**
* @template T
* @param callable(): T $factory
* @return T
*/
public function get(string $key, callable $factory): mixed
{
if (array_key_exists($key, $this->itemsByKey)) {
$value = $this->items[$key];
unset($this->items[$key]);
$this->items[$key] = $value;
return $value;
}
$value = $factory();
$this->items[$key] = $value;
if (count($this->items) > $this->capacity) {
array_shift($this->items);
}
return $value;
}
return static function (ContainerConfigurator $di): void {
$di->services()
->defaults()
->autowire()
->autoconfigure()
->set(MyService::class)
->tag('kernel.reset', ['method' => 'trololoshka']);
};
class Registry
{
// ...
public function reset(): void
{
// примерный код
foreach ($this->managers as $manager) {
$manager->clear();
}
}
}
xdebug.mode=profile
xdebug.output_dir=/path/to/profiles/dir
$ bin/analyzer summary dump.json
+----------+-----------------+-----------------------------+
| Type | Instances Count | Cumulated Self Size (bytes) |
+----------+-----------------+-----------------------------+
| string | 132 | 7079 |
| MyClassA | 100 | 7200 |
| array | 10 | 720 |
| integer | 5 | 80 |
| float | 2 | 32 |
| null | 1 | 16 |
+----------+-----------------+-----------------------------+
$ bin/analyzer top-children dump.json
+-----+----------------+----------+
| Num | Item ids | Children |
+-----+----------------+----------+
| 1 | 0x7ffff4e22fe0 | 1000000 |
| 2 | 0x7fffe780e5c8 | 11606 |
| 3 | 0x7fffe9714ef0 | 11602 |
| 4 | 0x7fffeab63ca0 | 3605 |
| 5 | 0x7fffd3161400 | 2400 |
+-----+----------------+----------+
@vudaltsov / Пых / PHP Point / PHP умирает?!