はじめに
今回はCysharp/Ulid
という.NET Core
やUnity
のためのランダムIDジェネレーターを紹介したいと思います。
Fast C# Implementation of ULID for .NET Core and Unity. Ulid is sortable, random id generator. This project aims performance by fastest binary serializer(MessagePack-CSharp) technology. It achives faster generate than Guid.NewGuid.
// DeepL翻訳
.NET CoreとUnityのためのULIDの高速C#実装。ULIDはソート可能なランダムIDジェネレータです。このプロジェクトでは、最速のバイナリー・シリアライザー(MessagePack-CSharp)技術によるパフォーマンスを狙っています。Guid.NewGuidよりも高速に生成することができます。
https://github.com/neuecc/MessagePack-CSharp
ulid
の仕様については以下のリポジトリに記載されています。
github.com
これをC#での利用を念頭に、パフォーマンスに気をつけながら作成されたライブラリが今回紹介するCysharp/Ulid
です(と私は解釈しています)。
github.com
また他にもC#
ライブラリは存在します。
GitHub - mcb2001/CSharp.Ulid: Universally Unique Lexicographically Sortable Identifier
UUIDとUlid
皆さんいつも使っているGuid.NewGuid
はUUID
の生成になります。
UUID(Universally Unique Identifier)とは、ソフトウェア上でオブジェクトを一意に識別するための識別子である。UUIDは128ビットの数値だが、16進法による550e8400-e29b-41d4-a716-446655440000というような文字列による表現が使われることが多い。元来は分散システム上で統制なしに作成できる識別子として設計されており、したがって将来にわたって重複や偶然の一致が起こらないという前提で用いることができる。マイクロソフトによるGUIDは、UUIDの実装の1つと見なせる。
ULID
も方向性としては同じですが、ミリ秒単位で時系列ソートできるというメリットがあります。
- 128-bit compatibility with UUID
- 1.21e+24 unique ULIDs per millisecond
- Lexicographically sortable
- Canonically encoded as a 26 character string, as opposed to the 36 character UUID
- Uses Crockford's base32 for better efficiency and readability (5 bits per character)
- Case insensitive
- No special characters (URL safe)
- Monotonic sort order (correctly detects and handles the same millisecond)
- UUIDと128ビットの互換性
- 1.21e+24個のユニークなULIDを1ミリ秒間に生成可能
- レキシログラフィックにソート可能
- 36文字のUUIDに対して、26文字の文字列として正規にエンコードされている
- 効率性と可読性を高めるためにCrockfordのbase32を使用(1文字あたり5ビット)
- 大文字と小文字を区別しない
- 特殊文字なし(URLセーフ)
- 単調なソート順(同じミリ秒を正しく検出し処理する)
Guid.NewGuid
(UUID
)と比較して今回のライブラリを導入することで以下のメリットが得られます。
- 生成時刻によるソート可
- 高速な生成可
どうやって生成時刻によるソートができているのかというと、Timestamp
とRandomness
に分かれて構成されているからですね。
01AN4Z07BY 79KA1307SR9X4MV3 Timestamp Randomness 48bits 80bits
Timestamp
- 48 bit integer
- UNIX-time in milliseconds
- Won't run out of space 'til the year 10889 AD.
Randomness
- 80 bits
- Cryptographically secure source of randomness, if possible
GitHub - ulid/spec: The canonical spec for ulid
TimeStamp
がms
であることから分かるように、同一ミリ秒だと順番が保証されませんので注意です。
Within the same millisecond, sort order is not guaranteed
そこまでデメリットはないとは思うのですが、ユーザーにULID
から生成時刻がバレちゃうとかはデメリットちゃデメリットなんですかね。
導入
Release
ページから最新のunitypackage
をダウンロード&インポートしてください。
github.com
依存先として以下がありますが、unitypackage
に同梱されています。
- System.Buffers.dll
- System.Memory.dll
- System.Runtime.CompilerServices.Unsafe.dll
使い方
Ulid.NewUlid()
Ulid.Parse()
Ulid.TryParse()
new Ulid()
.ToString()
.ToByteArray()
.TryWriteBytes()
.TryWriteStringify()
.ToBase64()
.Time
.Random
// 生成 : Ulid.NewUlid vs Guid.NewGuid Ulid ulid = Ulid.NewUlid(); Guid guid = Guid.NewGuid(); // ToString : Ulid.ToString vs Guid.ToString string ulidText = ulid.ToString(); // 01GNE8CEWWNZNK23JBCN197DM1 string guidText = guid.ToString(); // 8ec8aaca-ab96-4a0a-8f47-b77302567a6b // Parse : Ulid.Parse(string) vs Guid.Parse(string) Ulid ulid2 = Ulid.Parse(ulidText); Guid guid2 = Guid.Parse(guidText); // GetHashCode : Ulid.GetHashCode vs Guid.GetHashCode _ = ulid.GetHashCode(); _ = guid.GetHashCode(); // Equal : Ulid.Equals vs Guid.Equals Debug.Log(ulid.Equals(ulid2)); // True Debug.Log(guid.Equals(guid2)); // True // CompareTo : Ulid.CompareTo vs Guid.CompareTo Debug.Log(ulid.CompareTo(Ulid.NewUlid())); // -1 (後に生成された方が大きい, CompareToのイメージは「左-右」) Debug.Log(guid.CompareTo(Guid.NewGuid())); // ??? (一意でない) Debug.Log(ulid.Time); // 12/29/2022 06:16:19 +00:00 Debug.Log(BitConverter.ToString(ulid.Random)); // AF-EB-31-0E-4B-65-42-93-B6-81
パフォーマンス
メモリアロケーション・処理速度共に全体的に性能が向上しているようです。
GetHashCode
やEquals
はちょいGUID
に処理速度で負けているようですが、大した差ではないと思います。
それ以上にメリットの方が遥かに大きいような。