From 5ba1965cf48c46235ee68c64594dd3991aa5a509 Mon Sep 17 00:00:00 2001 From: abmmhasan Date: Tue, 30 Apr 2024 16:02:31 +0600 Subject: [PATCH 1/4] updated bench to ms --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ce33e30..4522152 100644 --- a/README.md +++ b/README.md @@ -237,21 +237,21 @@ Library exclusive. ## Benchmark -| Type | Total Duration (1M Generation, Single thread) | -|:---------------------------|:---------------------------------------------:| -| UUID v1 (random node) | 1.23968s | -| UUID v1 (fixed node) | 0.94521s | -| UUID v3 (custom namespace) | 0.76439s | -| UUID v4 | 0.70501s | -| UUID v5 (custom namespace) | 0.89831s | -| UUID v6 (random node) | 1.39867s | -| UUID v6 (fixed node) | 1.42344s | -| UUID v7 (random node) | 1.40466s | -| UUID v7 (fixed node) | 1.49268s | -| UUID v8 (random node) | 1.80438s | -| UUID v8 (fixed node) | 1.78257s | -| ULID | 1.79775s | -| TBSL | 0.80612s | +| Type | Generation time (ms) | +|:---------------------------|:--------------------:| +| UUID v1 (random node) | 0.00124 | +| UUID v1 (fixed node) | 0.00095 | +| UUID v3 (custom namespace) | 0.00076 | +| UUID v4 | 0.00071 | +| UUID v5 (custom namespace) | 0.0009 | +| UUID v6 (random node) | 0.0014 | +| UUID v6 (fixed node) | 0.00142 | +| UUID v7 (random node) | 0.0014 | +| UUID v7 (fixed node) | 0.00149 | +| UUID v8 (random node) | 0.0018 | +| UUID v8 (fixed node) | 0.00178 | +| ULID | 0.0018 | +| TBSL | 0.00081 | _Note: Snowflake & Sonyflake not included, due to their way of work_ From d1f6e90ddc660457f2ec22db55a819c34e1d2200 Mon Sep 17 00:00:00 2001 From: abmmhasan Date: Thu, 2 May 2024 22:06:08 +0600 Subject: [PATCH 2/4] fixed performance issue with flakes --- src/Exceptions/FileLockException.php | 8 +++++ src/GetSequence.php | 47 ++++++++++++++++++++++++++++ src/Snowflake.php | 46 +++------------------------ src/Sonyflake.php | 42 +++---------------------- 4 files changed, 64 insertions(+), 79 deletions(-) create mode 100644 src/Exceptions/FileLockException.php create mode 100644 src/GetSequence.php diff --git a/src/Exceptions/FileLockException.php b/src/Exceptions/FileLockException.php new file mode 100644 index 0000000..071bba6 --- /dev/null +++ b/src/Exceptions/FileLockException.php @@ -0,0 +1,8 @@ +format('Ymd') . '.seq'; + if (!file_exists(self::$fileLocation)) { + touch(self::$fileLocation); + } + $handle = fopen(self::$fileLocation, "r+"); + if (!flock($handle, LOCK_EX)) { + throw new FileLockException('Could not acquire lock on ' . self::$fileLocation); + } + $now = $dateTime->format('Uv'); + $line = fgetcsv($handle); + $sequence = 0; + if ($line) { + $sequence = match ($line[0]) { + $now => $line[1], + default => $sequence + }; + } + ftruncate($handle, 0); + rewind($handle); + fputcsv($handle, [$now, ++$sequence]); + flock($handle, LOCK_UN); + fclose($handle); + return $sequence; + } +} diff --git a/src/Snowflake.php b/src/Snowflake.php index 6fbd42c..fe1c926 100644 --- a/src/Snowflake.php +++ b/src/Snowflake.php @@ -3,18 +3,19 @@ namespace Infocyph\UID; use DateTimeImmutable; -use DateTimeInterface; use Exception; +use Infocyph\UID\Exceptions\FileLockException; use Infocyph\UID\Exceptions\SnowflakeException; class Snowflake { + use GetSequence; + private static int $maxTimestampLength = 41; private static int $maxDatacenterLength = 5; private static int $maxWorkIdLength = 5; private static int $maxSequenceLength = 12; private static ?int $startTime; - private static string $fileLocation; /** * Generates a unique snowflake ID. @@ -22,7 +23,7 @@ class Snowflake * @param int $datacenter The ID of the datacenter (default: 0) * @param int $workerId The ID of the worker (default: 0) * @return string The generated snowflake ID - * @throws SnowflakeException + * @throws SnowflakeException|FileLockException */ public static function generate(int $datacenter = 0, int $workerId = 0): string { @@ -41,8 +42,7 @@ public static function generate(int $datacenter = 0, int $workerId = 0): string $currentTime = (int)$now->format('Uv'); while (($sequence = self::sequence( $now, - $datacenter, - $workerId + $datacenter . $workerId )) > (-1 ^ (-1 << self::$maxSequenceLength))) { ++$currentTime; } @@ -119,40 +119,4 @@ private static function getStartTimeStamp(): float|int { return self::$startTime ??= (strtotime('2020-01-01 00:00:00') * 1000); } - - /** - * Generates a sequence number based on the current time. - * - * @param DateTimeInterface $now The current time. - * @param int $datacenter - * @param int $workerId - * @return int The generated sequence number. - * @throws SnowflakeException - */ - private static function sequence(DateTimeInterface $now, int $datacenter, int $workerId): int - { - self::$fileLocation = sys_get_temp_dir() . DIRECTORY_SEPARATOR . - 'uid-snf-' . $datacenter . $workerId . $now->format('Ymd') . '.seq'; - if (!file_exists(self::$fileLocation)) { - touch(self::$fileLocation); - } - $handle = fopen(self::$fileLocation, "r+"); - if (!flock($handle, LOCK_EX)) { - throw new SnowflakeException('Could not acquire lock on ' . self::$fileLocation); - } - $content = ''; - while (!feof($handle)) { - $content .= fread($handle, 1024); - } - $content = json_decode($content, true); - $currentTime = (int)$now->format('Uv'); - $content[$currentTime] = ($content[$currentTime] ?? 0) + 1; - ftruncate($handle, 0); - rewind($handle); - fwrite($handle, json_encode($content)); - flock($handle, LOCK_UN); - fclose($handle); - - return $content[$currentTime]; - } } diff --git a/src/Sonyflake.php b/src/Sonyflake.php index 7fac9fa..13408b1 100644 --- a/src/Sonyflake.php +++ b/src/Sonyflake.php @@ -3,24 +3,25 @@ namespace Infocyph\UID; use DateTimeImmutable; -use DateTimeInterface; use Exception; +use Infocyph\UID\Exceptions\FileLockException; use Infocyph\UID\Exceptions\SonyflakeException; class Sonyflake { + use GetSequence; + private static int $maxTimestampLength = 39; private static int $maxMachineIdLength = 16; private static int $maxSequenceLength = 8; private static ?int $startTime; - private static string $fileLocation; /** * Generates a unique identifier using the SonyFlake algorithm. * * @param int $machineId The machine identifier. Must be between 0 and the maximum machine ID. * @return string The generated unique identifier. - * @throws SonyflakeException + * @throws SonyflakeException|FileLockException */ public static function generate(int $machineId = 0): string { @@ -122,39 +123,4 @@ private static function elapsedTime(): int { return floor(((new DateTimeImmutable('now'))->format('Uv') - self::getStartTimeStamp()) / 10) | 0; } - - /** - * Generates a sequence number based on the current time. - * - * @param DateTimeInterface $now The current time. - * @param string $machineId The machine identifier. - * @return int The generated sequence number. - * @throws SonyflakeException - */ - private static function sequence(DateTimeInterface $now, string $machineId): int - { - self::$fileLocation = sys_get_temp_dir() . DIRECTORY_SEPARATOR . - 'uid-sof-' . $machineId . $now->format('Ymd') . '.seq'; - if (!file_exists(self::$fileLocation)) { - touch(self::$fileLocation); - } - $handle = fopen(self::$fileLocation, "r+"); - if (!flock($handle, LOCK_EX)) { - throw new SonyflakeException('Could not acquire lock on ' . self::$fileLocation); - } - $content = ''; - while (!feof($handle)) { - $content .= fread($handle, 1024); - } - $content = json_decode($content, true); - $currentTime = (int)$now->format('Uv'); - $content[$currentTime] = ($content[$currentTime] ?? 0) + 1; - ftruncate($handle, 0); - rewind($handle); - fwrite($handle, json_encode($content)); - flock($handle, LOCK_UN); - fclose($handle); - - return $content[$currentTime]; - } } From 170e887484da722c8d19985f7bf1cd277a557ae6 Mon Sep 17 00:00:00 2001 From: abmmhasan Date: Thu, 2 May 2024 22:29:28 +0600 Subject: [PATCH 3/4] updated file name --- src/GetSequence.php | 5 ++--- src/Snowflake.php | 3 ++- src/Sonyflake.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/GetSequence.php b/src/GetSequence.php index bd3887c..0d67c9b 100644 --- a/src/GetSequence.php +++ b/src/GetSequence.php @@ -17,10 +17,9 @@ trait GetSequence * @return int The generated sequence number. * @throws FileLockException */ - private static function sequence(DateTimeInterface $dateTime, string $machineId): int + private static function sequence(DateTimeInterface $dateTime, string $machineId, string $type): int { - self::$fileLocation = sys_get_temp_dir() . DIRECTORY_SEPARATOR . - 'uid-snf-' . $machineId . $dateTime->format('Ymd') . '.seq'; + self::$fileLocation = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "uid-$type-$machineId.seq"; if (!file_exists(self::$fileLocation)) { touch(self::$fileLocation); } diff --git a/src/Snowflake.php b/src/Snowflake.php index fe1c926..5f23ab9 100644 --- a/src/Snowflake.php +++ b/src/Snowflake.php @@ -42,7 +42,8 @@ public static function generate(int $datacenter = 0, int $workerId = 0): string $currentTime = (int)$now->format('Uv'); while (($sequence = self::sequence( $now, - $datacenter . $workerId + $datacenter . $workerId, + 'snowflake' )) > (-1 ^ (-1 << self::$maxSequenceLength))) { ++$currentTime; } diff --git a/src/Sonyflake.php b/src/Sonyflake.php index 13408b1..b3013e5 100644 --- a/src/Sonyflake.php +++ b/src/Sonyflake.php @@ -31,7 +31,7 @@ public static function generate(int $machineId = 0): string } $now = new DateTimeImmutable('now'); $elapsedTime = self::elapsedTime(); - while (($sequence = self::sequence($now, $machineId)) > (-1 ^ (-1 << self::$maxSequenceLength))) { + while (($sequence = self::sequence($now, $machineId, 'sonyflake')) > (-1 ^ (-1 << self::$maxSequenceLength))) { $nextMillisecond = self::elapsedTime(); while ($nextMillisecond === $elapsedTime) { ++$nextMillisecond; From d82cdc4518b1a166fc323adcdfa97c52858f8e68 Mon Sep 17 00:00:00 2001 From: abmmhasan Date: Fri, 3 May 2024 11:50:57 +0600 Subject: [PATCH 4/4] updated doc --- README.md | 63 +++++++++++++++++++++++++--------------------------- src/UUID.php | 15 +++++++------ 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 4522152..4495ebb 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ if you wanna track the source of the UUIDs, you should use it (pre-define the no ```php // Get v1 UUID (Time based) \Infocyph\UID\UUID::v1(); - // alternatively can also use \Infocyph\UID\uuid1(); @@ -71,8 +70,7 @@ if you wanna track the source of the UUIDs, you should use it (pre-define the no ```php // Get v3 UUID for 'TestString' -\Infocyph\UID\UUID::v3('TestString'); - +\Infocyph\UID\UUID::v3('a pre-generated UUID', 'the string you wanna get UUID for'); // alternatively can also use \Infocyph\UID\uuid3(); @@ -80,10 +78,10 @@ if you wanna track the source of the UUIDs, you should use it (pre-define the no * Get v3 UUID for an URL & pre-defined namespace * You can pass X500, URL, OID, DNS (check RFC4122 #Appendix C) */ -\Infocyph\UID\UUID::v3('abmmhasan.github.io','url'); +\Infocyph\UID\UUID::v3('url', 'abmmhasan.github.io'); // You can generate a random UUID & use as namespace as well -\Infocyph\UID\UUID::v3('abmmhasan.github.io','fa1700dd-828c-4d1b-8e6d-a6104807da90'); +\Infocyph\UID\UUID::v3('fa1700dd-828c-4d1b-8e6d-a6104807da90', 'abmmhasan.github.io'); ``` #### UUID v4: Random UUID. @@ -91,7 +89,6 @@ if you wanna track the source of the UUIDs, you should use it (pre-define the no ```php // Get v4 UUID (completely random) \Infocyph\UID\UUID::v4(); - // alternatively can also use \Infocyph\UID\uuid4(); ``` @@ -102,8 +99,7 @@ Better replacement for v3 due to better hashing algorithm (SHA1 instead of MD5). ```php // Get v5 UUID for 'TestString' -\Infocyph\UID\UUID::v5('TestString'); - +\Infocyph\UID\UUID::v5('a pre-generated UUID', 'the string you wanna get UUID for'); // alternatively can also use \Infocyph\UID\uuid5(); @@ -111,10 +107,10 @@ Better replacement for v3 due to better hashing algorithm (SHA1 instead of MD5). * Get v5 UUID for an URL & pre-defined namespace * You can pass X500, URL, OID, DNS (check RFC4122 #Appendix C) */ -\Infocyph\UID\UUID::v5('abmmhasan.github.io','url'); +\Infocyph\UID\UUID::v5('url', 'abmmhasan.github.io'); // You can generate a random UUID & use as namespace as well -\Infocyph\UID\UUID::v5('abmmhasan.github.io','fa1700dd-828c-4d1b-8e6d-a6104807da90'); +\Infocyph\UID\UUID::v5('fa1700dd-828c-4d1b-8e6d-a6104807da90', 'abmmhasan.github.io'); ``` #### UUID v6 (draft-based/unofficial): Time-based UUID. @@ -124,7 +120,6 @@ Better replacement for v1. Provides more randomness & uniqueness. ```php // Get v6 UUID (Time based) \Infocyph\UID\UUID::v6(); - // alternatively can also use \Infocyph\UID\uuid6(); @@ -135,14 +130,17 @@ Better replacement for v1. Provides more randomness & uniqueness. #### UUID v7 (draft-based/unofficial): Time-based UUID. ```php -// Get v7 UUID (Time based) +// Get v7 UUID for curren time \Infocyph\UID\UUID::v7(); - // alternatively can also use \Infocyph\UID\uuid7(); -// Pass your pre-generated node (for node specific UUID) -\Infocyph\UID\UUID::v7($node); // check additional section for how to generate one +// Pass your pre-generated node (for node specific UUID) for current time +\Infocyph\UID\UUID::v7(null, $node); // check additional section for how to generate one + +// Get v7 UUID for pre-define time using DateTimeInterface +$timeInterface = new DateTime(); // DateTime implements DateTimeInterface +\Infocyph\UID\UUID::v7($timeInterface); ``` #### UUID v8 (draft-based/unofficial): Time-based UUID. Lexicographically sortable. @@ -150,7 +148,6 @@ Better replacement for v1. Provides more randomness & uniqueness. ```php // Get v6 UUID (Time based) \Infocyph\UID\UUID::v8(); - // alternatively can also use \Infocyph\UID\uuid8(); @@ -237,23 +234,23 @@ Library exclusive. ## Benchmark -| Type | Generation time (ms) | -|:---------------------------|:--------------------:| -| UUID v1 (random node) | 0.00124 | -| UUID v1 (fixed node) | 0.00095 | -| UUID v3 (custom namespace) | 0.00076 | -| UUID v4 | 0.00071 | -| UUID v5 (custom namespace) | 0.0009 | -| UUID v6 (random node) | 0.0014 | -| UUID v6 (fixed node) | 0.00142 | -| UUID v7 (random node) | 0.0014 | -| UUID v7 (fixed node) | 0.00149 | -| UUID v8 (random node) | 0.0018 | -| UUID v8 (fixed node) | 0.00178 | -| ULID | 0.0018 | -| TBSL | 0.00081 | - -_Note: Snowflake & Sonyflake not included, due to their way of work_ +| Type | Generation time (ms) | +|:---------------------------|:---------------------------------------------------------------------------------:| +| UUID v1 (random node) | 0.00411 (ramsey/Uuid: 0.18753) | +| UUID v1 (fixed node) | 0.00115 (ramsey/Uuid: 0.17386) | +| UUID v3 (custom namespace) | 0.00257 (ramsey/Uuid: 0.03015) | +| UUID v4 | 0.00362 (ramsey/Uuid: 0.16501) | +| UUID v5 (custom namespace) | 0.00108 (ramsey/Uuid: 0.03658) | +| UUID v6 (random node) | 0.00444 (ramsey/Uuid: 0.17469) | +| UUID v6 (fixed node) | 0.00164 (ramsey/Uuid: 0.17382) | +| UUID v7 (random node) | 0.00503 (ramsey/Uuid: 0.16278) | +| UUID v7 (fixed node)** | 0.00154 (ramsey/Uuid: 0.18753) | +| UUID v8 (random node) | 0.00505 (ramsey/Uuid: N/A) | +| UUID v8 (fixed node) | 0.00209 (ramsey/Uuid: 0.16029 _*predefined random node, not usable as signature_) | +| ULID | 0.00506 (robinvdvleuten/php-ulid: 0.00508) | +| TBSL | 0.0034 (library unique) | +| Snowflake | 0.13951 (godruoyi/php-snowflake: 0.14856) | +| Sonyflake | 0.13821 (godruoyi/php-snowflake: 0.14583) | ## Support diff --git a/src/UUID.php b/src/UUID.php index ca11afa..b611f1d 100644 --- a/src/UUID.php +++ b/src/UUID.php @@ -65,12 +65,12 @@ public static function v1(string $node = null): string /** * Generates the v3 UUID for a given string using the specified namespace. * - * @param string $string The string to generate the hash for. * @param string $namespace The namespace to use for the hash generation. + * @param string $string The string to generate the hash for. * @return string * @throws UUIDException */ - public static function v3(string $string, string $namespace): string + public static function v3(string $namespace, string $string): string { $namespace = self::nsResolve($namespace); if (!$namespace) { @@ -95,12 +95,12 @@ public static function v4(): string /** * Generates the v3 UUID for a given string using the specified namespace. * - * @param string $string The string to generate the UUID from. - * @param string $namespace The namespace to use for the UUID generation. + * @param string $namespace The namespace to use for the hash generation. + * @param string $string The string to generate the hash for. * @return string * @throws UUIDException */ - public static function v5(string $string, string $namespace): string + public static function v5(string $namespace, string $string): string { $namespace = self::nsResolve($namespace); if (!$namespace) { @@ -134,13 +134,14 @@ public static function v6(string $node = null): string /** * Generates a version 7 UUID. * + * @param DateTimeInterface|null $dateTime An optional DateTimeInterface object to create the UUID. * @param string|null $node The node identifier. Defaults to null. * @return string * @throws Exception */ - public static function v7(string $node = null): string + public static function v7(?DateTimeInterface $dateTime = null, string $node = null): string { - $unixTsMs = (new DateTimeImmutable('now'))->format('Uv'); + $unixTsMs = ($dateTime ?? new DateTimeImmutable('now'))->format('Uv'); if ($unixTsMs <= self::$unixTsMs) { $unixTsMs = self::$unixTsMs + 1; }