UUID

ちょっと見てたのでまとめ。


UUID とは、Universally Unique Identifier の略で、分散システム上でどこかが統制を取らずとも、一意に特定可能な識別子。全部で 16 bytes (128 bits) の数字で表され、ABNF は以下のとおり。

UUID                   = time-low "-" time-mid "-"
                         time-high-and-version "-"
                         clock-seq-and-reserved
                         clock-seq-low "-" node
time-low               = 4hexOctet
time-mid               = 2hexOctet
time-high-and-version  = 2hexOctet
clock-seq-and-reserved = hexOctet
clock-seq-low          = hexOctet
node                   = 6hexOctet
hexOctet               = hexDigit hexDigit
hexDigit =
      "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" /
      "a" / "b" / "c" / "d" / "e" / "f" /
      "A" / "B" / "C" / "D" / "E" / "F"


RFC 4122 のなかでは、UUID の生成に関して、バージョン番号1、3、4、5 で識別される4つのアルゴリズムを規定。

  • Version1
    • MACアドレスとタイムスタンプが含まれるので、コンピュータの識別や生成時刻の特定が可能。ある種のセキュリティに注意すべきアプリケーションでは適さない。
  • Version3
    • 与えられた名前空間をもとに作成。ハッシュ方式に MD5 を使用。
  • Version4
    • ランダムな数から生成。
  • Version5
    • Version3 と似ているが、ハッシュ方式に SHA-1 を使用。その分安全。

で、Ruby での実装をみてみる。
wikipedia:en:uuid によると、以下の3つ。

  • Ruby-UUID: 最新バージョンは 2007.12.30 に CodeRepos に移行した 0.1
  • UUID: 最新バージョンは 2008.8.27 公開の 2.0.1
  • UUIDTools: 最新バージョンは 2008.12.4 公開の 1.0.7

Rubyforge で探すと uuid4r や guid とかも見つかる。

  • Guid: Guid is a Ruby library for portable GUID/UUID generation. At the moment it can be used on Windows (except first release of Win95 and older) and on Unix.
  • UUID4R: This is a wrapper of OSSP uuid C library, and can generate and parse UUID.


メンテナンス状況とかをみると、UUIDUUIDTools がアクティブっぽい。



さらにソースをみてみる。

uuidtools-1.0.7 は、実直にバージョン1、3、4、5 を実装している。
使い方は、それぞれこんな感じで、to_s すると 16 bytes の文字列が得られる。

UUID.timestamp_create
=> #<UUID:0x2adfdc UUID:64a5189c-25b3-11da-a97b-00c04fd430c8>
UUID.md5_create(UUID_DNS_NAMESPACE, "www.widgets.com")
=> #<UUID:0x287576 UUID:3d813cbb-47fb-32ba-91df-831e1593ac29>
UUID.random_create
=> #<UUID:0x19013a UUID:984265dc-4200-4f02-ae70-fe4f48964159>
UUID.sha1_create(UUID_DNS_NAMESPACE, "www.widgets.com")
=> #<UUID:0x2a0116 UUID:21f7f8de-8051-5b89-8680-0195ef798b6a>


一方の uuid-2.0.1 は、バージョン1 のみの実装。ある意味、男前。

uuid = UUID.new

10.times do
  p uuid.generate
end


ちなみに、前のバージョンである、uuid-1.0.4 の使い方は微妙に違っていた。 :(

10.times do
  p UUID.new
end

機能の差は歴然なので、ベンチマークをとってみる。比較対象は、いずれもバージョン1 のアルゴリズムで、UUID を1万回生成してみる。

% gem list --local uuid

*** LOCAL GEMS ***

uuid (2.0.1, 1.0.4)
uuidtools (1.0.7)

% ruby186 uuid_bench.rb uuidtools-1.0.7 10000
benchmark target: uuidtools-1.0.7
                                    user     system      total        real
UUID.timestamp_create.to_s:     0.720000   0.010000   0.730000 (  0.775910)

% ruby186 uuid_bench.rb uuid-1.0.4 10000
benchmark target: uuid-1.0.4
                                    user     system      total        real
UUID.new:                       0.150000   0.000000   0.150000 (  0.156139)

% ruby186 uuid_bench.rb uuid-2.0.1 10000
benchmark target: uuid-2.0.1
                                    user     system      total        real
@uuid.generate:                 0.140000   0.010000   0.150000 (  0.150422)
UUID.generate:                  0.160000   0.000000   0.160000 (  0.164215)
UUID.new.generate:              1.030000   1.260000   2.290000 ( 13.347787)


API は (機能が少ない分) わかりやすいので、セキュリティの問題がなければ、uuid-x.x.x で十分かも?
# 最後の UUID.new.generate はふつうやらないと思うけど、一応やってみた。 :-p



以下、ベンチマークに使用したプログラム。

require 'benchmark'
require 'rubygems'

puts "Usage: ruby uuid_bench.rb target_uuid repeat_count" if ARGV.size < 2
name, version = ARGV[0].split("-")
count = ARGV[1].to_i

# switch version
gem "#{name}", "= #{version}"
require "#{name}"
puts "benchmark target: #{name}-#{version}"

def iterate(count, &block)
  count.times do
    yield block
  end
end

# main
Benchmark.bm(30) do |x|
  case name
  when "uuid"
    uuid = UUID.new
    if uuid.kind_of? String    # uuid-1.x.x
      x.report("UUID.new:") {iterate(count) {UUID.new}}
    elsif uuid.kind_of? UUID   # uuid-2.x.x
      x.report("@uuid.generate:") {iterate(count) {uuid.generate}}
      x.report("UUID.generate:") {iterate(count) {UUID.generate}}
      x.report('UUID.new.generate:') {iterate(count) {UUID.new.generate}}
    end
  when "uuidtools"
    x.report('UUID.timestamp_create.to_s:') {iterate(count) {UUID.timestamp_create.to_s}}
  end
end