A few days ago, I released my weekend project called ffi-uuid, via gemcutter to rubygems.org. It uses the Ruby FFI (Foreign Function Interface) gem to wrap libuuid calls. It required learning basic FFI and reading examples and only just enough to get this working.
I knew that libuuid is what I wanted to use but didn’t want to write/maintain a bunch of C just to talk to it. Writing an extension, testing it on 1.8, 1.9 and on multiple platforms made it very unappealing. The biggest issues with uuidgen are that it doesn’t support getting a list of UUIDs and it’s not always the same on each platform. OS X uuidgen doesn’t support the same options as linux.
Shortly before giving up and hacking an extention, I came across FFI and it’s perfect for this situation. Read more about FFI here: http://github.com/ffi/ffi.
The ffi-uuid gem homepage is http://rubygems.org/gems/ffi-uuid (source is on github)
Hopefully, some other smart folks will make it better.
It also required learning jeweler and gemcutter to get the gem publishing process going but it’s all pretty easy and certainly better than the manual process.
The original approach for getting UUID’s in Ruby used some other uuid gems which were cumbersome. These were replaced with a call to the e2fsprogs command: uuidgen to make life easier.
The method looked like this:
def new_uuid
`uuidgen -t`.strip
end
Nothing fancy and does the job. The ‘-t’ means time based whereas ‘-r’ is random. The result is something like this “84e15bb0-d248-11df-9307-00237dd0b9fa”
The problem was that doing this a lot is very slow. I added some caching around a bash ‘for’ loop to avoid the system command overhead which hit uuidgen a bunch and then parsed the result into a stack and it’s a lot faster but nowhere close to the FFI approach.
Here’s why switching again is worthwhile.
1,000,000 uuids:
bash/for/uuidgen 16 minutes (miserably slow)
ffi-uuid 20 seconds (wicked fast)
That’s minutes vs seconds!
The bash approach is really bad (but still faster than the prior `uuidgen` call). Bash/for/uuidgen has a ridiculous amount of overhead because I had to run it in a forked process because it chews through file handles trying to work with uuidgen and they don’t get reclaimed fast enough.
Here’s the timing for all 3 approaches as a reference.
10,000 uuids: (that's only 10k - very small number for my usage)
`uuidgen -t`.strip 77.42 seconds (unbearably slow)
bash/for/uuidgen 9.70 seconds (noticeably slow)
ffi-uuid 0.17 seconds (not a hint of slow)
To run it
- gem install ffi
- gem install ffi-uuid
Here’s a simple test program to show the usage.
require 'rubygems'
require 'ffi-uuid'
def new_uuid
FFI::UUID.generate # get just one
end
10.times { puts new_uuid }
It ended up being so fast, that I don’t need any caching and get to delete a bunch of code – always a good thing.
From this experience, I learned that FFI is much easier to deal with than writing extensions and it’s nothing to fear. You may still have to write C or find a library that does the primary function but it not nearly as complicated as writing an extension and testing the library code first should be more straightforward.
cheers
michael
Great gem, super fast. Needed it to work on our dev machines also, which are Macs, do made the following modification to account for the lack of libuuid
ffi_lib(["libSystem.B", "uuid", "libuuid", "libuuid.so", "libuuid.so.1"])
Awesome to hear that someone else is using the gem.
Thanks for the addition of libSystem.B.
I also use a Mac for some development but had taken the ports/fink route with getting libuuid running which is a big hassle.
This will make it a lot simpler to get running.
The gem at ver 0.1.3 has this addition.
I wanted to allow libuuid usage so the list order is a little different.
Let me know if it presents any problem.