Skip to content

Instantly share code, notes, and snippets.

@io-developer
Last active May 17, 2021 23:26
Show Gist options
  • Save io-developer/00efd4b06929dcbc86ce9ec0d86c5945 to your computer and use it in GitHub Desktop.
Save io-developer/00efd4b06929dcbc86ce9ec0d86c5945 to your computer and use it in GitHub Desktop.
<?php
abstract class Struct
{
protected static array $defaultProps = [];
protected static string $packFmt = '';
protected static string $unpackFmt = '';
protected array $props = [];
public function __construct()
{
$this->props = static::$defaultProps;
}
public function __get($name)
{
if (isset(static::$defaultProps[$name])) {
return $this->props[$name];
}
return null;
}
public function __set($name, $value)
{
if (isset(static::$defaultProps[$name])) {
$this->props[$name] = $value;
}
}
public function pack(): string
{
return pack(static::$packFmt, ...array_values($this->props));
}
/**
* @param string $data
* @return $this
*/
public function unpack(string $data)
{
$this->props = unpack(static::$unpackFmt, $data);
return $this;
}
}
/**
* @property int $header
* @property int $heapCount
*/
class Meta extends Struct
{
const HEADER = 0x8f19;
const SIZE = 2 + 8;
const ALLOCATED_BUCKETS = 8192;
const HEAP_OFFSET = self::SIZE + self::ALLOCATED_BUCKETS * Bucket::SIZE;
protected static string $packFmt = 'SQ';
protected static string $unpackFmt = 'Sheader/QheapCount';
protected static array $defaultProps = [
'header' => self::HEADER,
'heapCount' => 0,
];
public function isValidHeader(): bool
{
return $this->header == self::HEADER;
}
public function getFreeOffset(): int
{
return self::HEAP_OFFSET + $this->heapCount * Bucket::SIZE;
}
public static function getBucketOffset(int $index): int
{
return self::SIZE + Bucket::SIZE * $index;
}
}
/**
* @property int $header
* @property int $key
* @property int $value
* @property int $nextOffset
*/
class Bucket extends Struct
{
const HEADER = 0x64e9;
const SIZE = 2 + 8 + 8 + 8;
protected static string $packFmt = 'SQQQ';
protected static string $unpackFmt = 'Sheader/Qkey/Qvalue/QnextOffset';
protected static array $defaultProps = [
'header' => self::HEADER,
'key' => 0,
'value' => 0,
'nextOffset' => 0,
];
public function isValidHeader(): bool
{
return $this->header == self::HEADER;
}
}
class IntIntMap
{
private $shmop;
private int $shmopSize;
public function __construct($shmopId, int $shmopSize)
{
$this->shmop = $shmopId;
$this->shmopSize = $shmopSize;
}
public function get(int $key): ?int
{
$offset = Meta::getBucketOffset($this->keyToIndex($key));
$bucket = $this->readBucket($offset);
while ($bucket->isValidHeader() && $bucket->key != $key && $bucket->nextOffset >= Meta::HEAP_OFFSET) {
$bucket = $this->readBucket($bucket->nextOffset);
}
if ($bucket->isValidHeader() && $bucket->key == $key) {
return $bucket->value;
}
return null;
}
public function put(int $key, int $val): ?int
{
$this->lock();
// validate & init
$meta = $this->readMeta();
if (!$meta->isValidHeader()) {
$meta = new Meta();
$this->writeMeta($meta);
}
$oldVal = null;
$offset = Meta::getBucketOffset($this->keyToIndex($key));
$bucket = $this->readBucket($offset);
while ($bucket->isValidHeader() && $bucket->key != $key && $bucket->nextOffset >= Meta::HEAP_OFFSET) {
$offset = $bucket->nextOffset;
$bucket = $this->readBucket($offset);
}
// write allocated
if (!$bucket->isValidHeader()) {
$bucket = new Bucket();
$bucket->key = $key;
$bucket->value = $val;
$this->writeBucket($bucket, $offset);
$this->unlock();
return null;
}
// update existing
if ($bucket->key == $key) {
$oldVal = $bucket->value;
$bucket->value = $val;
$this->writeBucket($bucket, $offset);
$this->unlock();
return $oldVal;
}
// add next bucket
$nextBucket = new Bucket();
$nextBucket->key = $key;
$nextBucket->value = $val;
$this->writeBucket($nextBucket, $meta->getFreeOffset());
$bucket->nextOffset = $meta->getFreeOffset();
$this->writeBucket($bucket, $offset);
$meta->heapCount += 1;
$this->writeMeta($meta);
$this->unlock();
return null;
}
public function keyToIndex(int $key): int
{
return $key % Meta::ALLOCATED_BUCKETS;
}
private function readMeta(): Meta
{
return (new Meta())->unpack($this->read(0, Meta::SIZE));
}
private function writeMeta(Meta $meta)
{
$this->write($meta->pack(), 0);
}
private function readBucket(int $offset): Bucket
{
return (new Bucket())->unpack($this->read($offset, Bucket::SIZE));
}
private function writeBucket(Bucket $bucket, int $offset)
{
$this->write($bucket->pack(), $offset);
}
private function read(int $offset, int $size)
{
return shmop_read($this->shmop, $offset, $size);
}
private function write(string $data, int $offset)
{
return shmop_write($this->shmop, $data, $offset);
}
private function lock()
{
}
private function unlock()
{
}
}
$id = ftok(__FILE__, 't');
$size = 16 * (1 << 20);
$shmop = shmop_open($id, 'c', 0644, $size);
for ($i = 0; $i < 4000; $i++) {
$key = (int)(1000.0 * microtime(true));
$map = new IntIntMap($shmop, $size);
$curVal = $map->get($key);
$newVal = microtime(true) % 777;
$oldVal = $map->put($key, $newVal);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment