Forum: Ruby generate UUID

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Aaron S. (Guest)
on 2007-03-08 23:44
Anyone have a fast way of generating a verson 4 UUID?

I have found some libraries to do this, but it seems like it could be
done in about 8 lines of code. Any faster approches?

what about this?

def rand_hex(l)
    validChars = ("A".."F").to_a + ("0".."9").to_a
    length = validChars.size
    hexCode = ""
    1.upto(l) do |i|
      hexCode << validChars[rand(length-1)]
    end
    hexCode
end

r =
"#{rand_hex(8)}-#{rand_hex(4)}-#{rand_hex(4)}-#{rand_hex(4)}-#{rand_hex(12)}"
puts r


thanks
Robert K. (Guest)
on 2007-03-09 00:11
(Received via mailing list)
On 08.03.2007 22:44, Aaron S. wrote:
>     hexCode = ""
>     1.upto(l) do |i|
>       hexCode << validChars[rand(length-1)]
>     end
>     hexCode
> end
>
> r =
> "#{rand_hex(8)}-#{rand_hex(4)}-#{rand_hex(4)}-#{rand_hex(4)}-#{rand_hex(12)}"
> puts r

Make validChars and length constants declared outside the method.  And
also, use rand(length) otherwise you never get a "9".  You could also
use ?A..?F and ?0..?9 - no idea whether that makes a performance diff.

But I guess this one is even faster:

def rand_hex_2(l)
   rand(1 << (l*4)).to_a(16)
end

Kind regards

  robert
Aaron S. (Guest)
on 2007-03-09 00:17
Robert K. wrote:
> On 08.03.2007 22:44, Aaron S. wrote:
>>     hexCode = ""
>>     1.upto(l) do |i|
>>       hexCode << validChars[rand(length-1)]
>>     end
>>     hexCode
>> end
>>
>> r =
>> "#{rand_hex(8)}-#{rand_hex(4)}-#{rand_hex(4)}-#{rand_hex(4)}-#{rand_hex(12)}"
>> puts r
>
> Make validChars and length constants declared outside the method.  And
> also, use rand(length) otherwise you never get a "9".  You could also
> use ?A..?F and ?0..?9 - no idea whether that makes a performance diff.
>
> But I guess this one is even faster:
>
> def rand_hex_2(l)
>    rand(1 << (l*4)).to_a(16)
> end
>
> Kind regards
>
>   robert


def rand_hex_2(l)
   rand (1 << (l*4) ).to_a(16)
end
puts rand_hex_2(9)

ArgumentError: wrong number of arguments (1 for 0)
Eden L. (Guest)
on 2007-03-09 03:24
(Received via mailing list)
He probably meant to_s(16), not to_a(16).  Anyway, rand_hex_2 won't
zero-pad, so you might want to replace the to_s with a format
operator:

def rand_hex_3(l)
  "%0#{n}x" % rand(1 << n*4)
end

def rand_uuid
  [8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-')
end

puts rand_uuid
Aaron S. (Guest)
on 2007-03-09 03:38
Eden Li wrote:
> He probably meant to_s(16), not to_a(16).  Anyway, rand_hex_2 won't
> zero-pad, so you might want to replace the to_s with a format
> operator:
>
> def rand_hex_3(l)
>   "%0#{n}x" % rand(1 << n*4)
> end
>
> def rand_uuid
>   [8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-')
> end
>
> puts rand_uuid

cool, thanks. just to clarify. couple variable names needed changing

def rand_hex_3(l)
  "%0#{l}x" % rand(1 << l*4)
end

def rand_uuid
  [8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-')
end

puts rand_uuid
Ryan D. (Guest)
on 2007-03-12 00:01
(Received via mailing list)
On Mar 8, 2007, at 5:38 PM, Aaron S. wrote:

We're using uuidtools at work and it is quite possibly the worst code
I've ever come across. Convoluted, obfuscated for the sake of
"security" and slow as hell.

% ./blah.rb 100
...
uuidtools-original    0.130000   4.650000   4.780000 (  4.782855)

Yes, 21 uuids per second!

I got a 400x improvement out of it by dropping this into our config/
environment.rb:

def UUID.true_random
   (1..8).to_a.map { rand(0x10000) }.pack("n8")
end

% ./blah.rb 100000
./blah.rb:9: warning: redefine true_random
...
uuidtools-modified   11.470000   0.010000  11.480000 ( 11.490444)

> def rand_hex_3(l)
>   "%0#{l}x" % rand(1 << l*4)
> end
>
> def rand_uuid
>   [8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-')
> end

This is clever. I really like it. To take the above solution a couple
steps further:

% ./blah.rb 1000000
# of iterations = 1000000
                           user     system      total        real
null_time             0.120000   0.000000   0.120000 (  0.116494)
benchmark-1          28.370000   0.050000  28.420000 ( 28.628063) #
original
benchmark-2          15.320000   0.010000  15.330000 ( 15.339800) #
inline and remove join
benchmark-3          10.500000   0.000000  10.500000 ( 10.521271) #
unroll the loop, fully
benchmark-4           8.600000   0.010000   8.610000 (  8.614185) #
reorg to remove bignum

-----

require 'benchmark'

max = (ARGV.shift || 1_000_000).to_i

def rand_hex_3(l)
   "%0#{l}x" % rand(1 << l*4)
end

def rand_uuid # original
   [8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-')
end

def rand_uuid2 # inline and remove join
   "%08x-%04x-%04x-%04x-%012x" % [8,4,4,4,12].map {|n| rand(1 << n*4) }
end

def rand_uuid3 # unroll the loop
   "%08x-%04x-%04x-%04x-%012x" % [
                                  rand(0x0000100000000),
                                  rand(0x0000000010000),
                                  rand(0x0000000010000),
                                  rand(0x0000000010000),
                                  rand(0x1000000000000),
                                 ]
end

def rand_uuid4 # remove bignums
   "%04x%04x-%04x-%04x-%04x-%06x%06x" % [
                                         rand(0x0010000),
                                         rand(0x0010000),
                                         rand(0x0010000),
                                         rand(0x0010000),
                                         rand(0x0010000),
                                         rand(0x1000000),
                                         rand(0x1000000),
                                        ]
end

puts "# of iterations = #{max}"
Benchmark::bm(20) do |x|
   x.report("null_time") do
     for i in 0..max do
       # do nothing
     end
   end

   x.report("benchmark-1") do
     for i in 0..max do
       rand_uuid
     end
   end

   x.report("benchmark-2") do
     for i in 0..max do
       rand_uuid2
     end
   end

   x.report("benchmark-3") do
     for i in 0..max do
       rand_uuid3
     end
   end

   x.report("benchmark-4") do
     for i in 0..max do
       rand_uuid4
     end
   end
end
Gary W. (Guest)
on 2007-03-12 02:17
(Received via mailing list)
On Mar 11, 2007, at 6:00 PM, Ryan D. wrote:
> Yes, 21 uuids per second!
>
> I got a 400x improvement out of it by dropping this into our config/
> environment.rb:
>
> def UUID.true_random
>   (1..8).to_a.map { rand(0x10000) }.pack("n8")
> end

I couldn't tell from the message if Ryan or Aaron wrote the
parts I quoted above...

I'm not an expert on UUIDs but I think there is a real
semantic difference between a UUID and a random number
of the same (binary) length.  RFC 4122 has lots of good
information on UUIDs: http://www.ietf.org/rfc/rfc4122.txt

Gary W.
Ryan D. (Guest)
on 2007-03-12 03:04
(Received via mailing list)
On Mar 11, 2007, at 5:16 PM, Gary W. wrote:

> I couldn't tell from the message if Ryan or Aaron wrote the
> parts I quoted above...

I wrote all those parts.

> I'm not an expert on UUIDs but I think there is a real
> semantic difference between a UUID and a random number
> of the same (binary) length.  RFC 4122 has lots of good
> information on UUIDs: http://www.ietf.org/rfc/rfc4122.txt

No, you're right, but for the most part, it really doesn't matter for
any of my company's uses. The most popular UUID format I've seen uses
the mac address as part of the encoding. I don't know of any
portable way for a ruby script to get that, and with all our UUIDs
being generated from a single server for the foreseeable future...
yeah. Not too useful.

In the specific case of uuidtools, it actually goes through WAY more
convolutions that ever necessary and (according to the linux manpage
on /dev/urandom) actually REDUCES the randomness of the UUIDs that it
generates. I just couldn't live with my test times anymore. 30% was
being spent on generating UUIDs that weren't any better than my
random blob. Remember, 21 uuids per second. *blech*

I mostly wrote the rest of the code to illustrate additional methods
to get more performance out of (pure) ruby.
Assaf (Guest)
on 2007-03-12 20:01
(Received via mailing list)
On Mar 11, 6:04 pm, Ryan D. <removed_email_address@domain.invalid> wrote:
> yeah. Not too useful.
A random number will work fine if you want something that looks like a
UUID. But if you want a Universally Unique Identifier, the time-based
UUID works better. It uses the clock time and a restart sequence to
gaurantee unique identifiers for the same machine, combined with the
Mac address to guarantee universal uniqueness (aka unique in space and
time).

On Windows you can use ipconfig to get the MAC address, and on Linux/
BSD/OS/X use ifconfig. There's no portable way, but you can run both
and see which answer you get.

Which is exactly what this UUID library does (disclosure: I'm the
author):

http://rubyforge.org/projects/reliable-msg


These are the numbers I get for generating 1 million UUIDs on a Duo
Core 2. Hopefully they're fast enough for you:

> cat test.rb
require "lib/uuid.rb"
1000000.times { UUID.new }

> time ruby test.rb
uuid: Initialized UUID generator with sequence number 0x6832 and MAC
address 00:13:A9:8A:FA:AA

real    0m27.983s
user    0m18.796s
sys     0m9.051s


Assaf
http://labnotes.org
Aaron S. (Guest)
on 2007-03-12 21:53
For what i'm working with it won't really matter. Trying to use as
little 3rd party libraries as possible. Not that yours isn't good
enough, just not wanting to add more dependencies to the project.
Eden L. (Guest)
on 2007-03-14 04:43
(Received via mailing list)
You might want to consider renaming them in your project so that
someone else that inherits your code won't think that they're really
universally unique.  As it stands right now, the methods above will
definitely allow collisions, and will cause headaches (and possible
data loss...) if someone uses them with that assumption in mind.
This topic is locked and can not be replied to.