Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ test.php
var
vendor
.codex
plan.md
281 changes: 152 additions & 129 deletions README.md

Large diffs are not rendered by default.

198 changes: 198 additions & 0 deletions benchmarks/CacheBulkBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?php

declare(strict_types=1);

namespace Infocyph\CacheLayer\Benchmarks;

use Infocyph\CacheLayer\Cache\Adapter\ArrayCacheAdapter;
use Infocyph\CacheLayer\Cache\Cache;
use PhpBench\Attributes as Bench;

#[Bench\Iterations(5)]
#[Bench\Revs(1)]
final class CacheBulkBench
{
private Cache $cache;

/** @var list<string> */
private array $keys = [];

private ?string $sqliteFile = null;

/** @var array<string, int> */
private array $values = [];

/** @param array{size:int} $params */
public function setUp(array $params): void
{
$this->cache = Cache::memory('bulk-bench');
$this->keys = [];
$this->values = [];
for ($index = 0; $index < $params['size']; $index++) {
$key = 'key.' . $index;
$this->keys[] = $key;
$this->values[$key] = $index;
}
$this->cache->setMultiple($this->values, 60);
}

/** @param array{size:int} $params */
#[Bench\ParamProviders('provideSizes')]
#[Bench\BeforeMethods('setUp')]
public function benchMemoryDeleteMultiple(array $params): int
{
unset($params);

return $this->cache->deleteMultiple($this->keys) ? count($this->keys) : 0;
}

/** @param array{size:int} $params */
#[Bench\ParamProviders('provideSizes')]
#[Bench\BeforeMethods('setUp')]
public function benchMemoryGetMultiple(array $params): int
{
unset($params);

return count($this->cache->getMultiple($this->keys));
}

/** @param array{size:int} $params */
#[Bench\ParamProviders('provideSizes')]
#[Bench\BeforeMethods('setUp')]
public function benchMemorySetMultiple(array $params): int
{
unset($params);

return $this->cache->setMultiple($this->values, 60) ? count($this->values) : 0;
}

public function benchSingleMemoryHit(): int
{
$cache = Cache::memory('single-memory');
$cache->set('hot', 42);

return (int) $cache->get('hot');
}

#[Bench\BeforeMethods('setUpSingleSqlite')]
#[Bench\AfterMethods('tearDownSqlite')]
public function benchSingleSqliteHit(): int
{
return (int) $this->cache->get('hot');
}

/** @param array{size:int} $params */
#[Bench\ParamProviders('provideSizes')]
#[Bench\BeforeMethods('setUpSqlite')]
#[Bench\AfterMethods('tearDownSqlite')]
public function benchSqliteGetMultiple(array $params): int
{
unset($params);

return count($this->cache->getMultiple($this->keys));
}

/** @param array{size:int} $params */
#[Bench\ParamProviders('provideSizes')]
#[Bench\BeforeMethods('setUp')]
public function benchTaggedBatchRead(array $params): int
{
unset($params);
foreach ($this->values as $key => $value) {
$this->cache->setTagged($key, $value, ['bulk']);
}

return count($this->cache->getMultiple($this->keys));
}

/** @param array{size:int} $params */
#[Bench\ParamProviders('provideSizes')]
public function benchTieredL1FullHit(array $params): int
{
$l1 = new ArrayCacheAdapter('tier-full-l1');
$l2 = new ArrayCacheAdapter('tier-full-l2');
$cache = Cache::tiered([$l1, $l2]);
$keys = [];
for ($index = 0; $index < $params['size']; $index++) {
$key = 'tier-full.' . $index;
$keys[] = $key;
$l1->set($key, $index, 60);
}

return count($cache->getMultiple($keys));
}

/** @param array{size:int} $params */
#[Bench\ParamProviders('provideSizes')]
public function benchTieredL3Promotion(array $params): int
{
$l1 = new ArrayCacheAdapter('tier3-l1');
$l2 = new ArrayCacheAdapter('tier3-l2');
$l3 = new ArrayCacheAdapter('tier3-l3');
$cache = Cache::tiered([$l1, $l2, $l3]);
$keys = [];
for ($index = 0; $index < $params['size']; $index++) {
$key = 'tier3.' . $index;
$keys[] = $key;
$l3->set($key, $index, 60);
}

return count($cache->getMultiple($keys));
}

/** @param array{size:int} $params */
#[Bench\ParamProviders('provideSizes')]
public function benchTieredPartialHit(array $params): int
{
$l1 = new ArrayCacheAdapter('tier-l1');
$l2 = new ArrayCacheAdapter('tier-l2');
$cache = Cache::tiered([$l1, $l2]);
$keys = [];
for ($index = 0; $index < $params['size']; $index++) {
$key = 'tier.' . $index;
$keys[] = $key;
$target = $index % 2 === 0 ? $l1 : $l2;
$target->set($key, $index, 60);
}

return count($cache->getMultiple($keys));
}

public function provideSizes(): iterable
{
yield '10 keys' => ['size' => 10];
yield '100 keys' => ['size' => 100];
yield '1000 keys' => ['size' => 1000];
}

public function setUpSingleSqlite(): void
{
$this->sqliteFile = sys_get_temp_dir() . '/cachelayer-bench-' . uniqid() . '.sqlite';
$this->cache = Cache::sqlite('sqlite-single-bench', $this->sqliteFile);
$this->cache->set('hot', 42, 60);
}

/** @param array{size:int} $params */
public function setUpSqlite(array $params): void
{
$this->sqliteFile = sys_get_temp_dir() . '/cachelayer-bench-' . uniqid() . '.sqlite';
$this->cache = Cache::sqlite('sqlite-bench', $this->sqliteFile);
$this->keys = [];
$this->values = [];
for ($index = 0; $index < $params['size']; $index++) {
$key = 'sqlite.' . $index;
$this->keys[] = $key;
$this->values[$key] = $index;
}
$this->cache->setMultiple($this->values, 60);
}

public function tearDownSqlite(): void
{
$this->cache = Cache::memory('released');
if ($this->sqliteFile !== null && is_file($this->sqliteFile)) {
unlink($this->sqliteFile);
}
$this->sqliteFile = null;
}
}
126 changes: 126 additions & 0 deletions benchmarks/CachePolicyBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

declare(strict_types=1);

namespace Infocyph\CacheLayer\Benchmarks;

use Infocyph\CacheLayer\Cache\Adapter\ArrayCacheAdapter;
use Infocyph\CacheLayer\Cache\Adapter\CachePayloadCodec;
use Infocyph\CacheLayer\Cache\Cache;
use Infocyph\CacheLayer\Cache\CacheOptions;
use Infocyph\CacheLayer\Cache\Lock\LockHandle;
use Infocyph\CacheLayer\Cache\Lock\LockProviderInterface;
use PhpBench\Attributes as Bench;

#[Bench\Iterations(5)]
#[Bench\Revs(500)]
final class CachePolicyBench
{
private string $payload;

public function __construct()
{
$this->payload = str_repeat('cachelayer-payload-', 256);
}

/** @param array{threshold:int} $params */
#[Bench\ParamProviders('provideCompressionThresholds')]
public function benchCompressedCodec(array $params): int
{
$codec = new CachePayloadCodec(new CacheOptions(compressionThreshold: $params['threshold']));
$record = $codec->decode($codec->encode($this->payload, null));

return strlen((string) $record?->value);
}

public function benchHmacCodec(): int
{
$codec = new CachePayloadCodec(new CacheOptions(integrityKey: 'benchmark-secret'));
$record = $codec->decode($codec->encode($this->payload, null));

return strlen((string) $record?->value);
}

public function benchPlainGet(): int
{
$cache = Cache::memory('plain-get');
$cache->set('hot', 42);

return (int) $cache->get('hot');
}

public function benchPlainSet(): int
{
return Cache::memory('plain-set')->set('key', 42) ? 1 : 0;
}

public function benchRememberContendedTimeout(): int
{
$lock = new class implements LockProviderInterface {
public function acquire(string $key, float $waitSeconds, float $leaseSeconds = 30.0): ?LockHandle
{
unset($key, $waitSeconds, $leaseSeconds);

return null;
}

public function refresh(?LockHandle $handle, float $leaseSeconds): bool
{
unset($handle, $leaseSeconds);

return false;
}

public function release(?LockHandle $handle): void
{
unset($handle);
}
};
$cache = new Cache(new ArrayCacheAdapter('contended'), $lock);

return (int) $cache->remember('cold', static fn(): int => 42, 60);
}

public function benchRememberHit(): int
{
$cache = Cache::memory('remember-hit');
$cache->set('hot', 42);

return (int) $cache->remember('hot', static fn(): int => 0, 60);
}

public function benchRememberMiss(): int
{
$cache = Cache::memory('remember-miss');

return (int) $cache->remember('cold', static fn(): int => 42, 60);
}

public function benchTaggedGet(): int
{
$cache = Cache::memory('tagged-get');
$cache->setTagged('hot', 42, ['group']);

return (int) $cache->get('hot');
}

public function benchTaggedSet(): int
{
return Cache::memory('tagged-set')->setTagged('key', 42, ['group']) ? 1 : 0;
}

public function benchUnsignedCodec(): int
{
$codec = new CachePayloadCodec();
$record = $codec->decode($codec->encode($this->payload, null));

return strlen((string) $record?->value);
}

public function provideCompressionThresholds(): iterable
{
yield '512 bytes' => ['threshold' => 512];
yield '4096 bytes' => ['threshold' => 4096];
yield '8192 bytes' => ['threshold' => 8192];
}
}
31 changes: 31 additions & 0 deletions benchmarks/ClosureSerializerBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Infocyph\CacheLayer\Benchmarks;

use Infocyph\CacheLayer\Serializer\ClosureSerializer;
use PhpBench\Attributes as Bench;

#[Bench\Iterations(5)]
#[Bench\Revs(1000)]
final class ClosureSerializerBench
{
public function benchSerializeUnserializeClosure(): int
{
$closure = static fn(int $value): int => $value + 5;
$payload = ClosureSerializer::serialize($closure);
$restored = ClosureSerializer::unserialize($payload);

return $restored(10);
}

public function benchSignedClosure(): int
{
$serializer = ClosureSerializer::signed('benchmark-signing-key');
$payload = $serializer->serialize(static fn(int $value): int => $value + 5);
$restored = $serializer->unserialize($payload);

return $restored(10);
}
}
11 changes: 11 additions & 0 deletions benchmarks/MemoizeBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,15 @@ public function benchObjectMemoizeHit(): int

return $sum;
}

#[Bench\BeforeMethods(['setUp'])]
public function benchOnceCallSiteLookup(): int
{
$sum = 0;
for ($index = 0; $index < 100; $index++) {
$sum += once(static fn(): int => 21);
}

return $sum;
}
}
Loading