Last active
May 17, 2021 23:26
-
-
Save io-developer/00efd4b06929dcbc86ce9ec0d86c5945 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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