December 28, 2010

Ruby String XOR Optimizations

As part of the LazyRaid project I spent a non trivial amount of time trying to develop an algorithm to perform an XOR operation in ruby against two blocks of data read from files. My initial try was a pure ruby implementation and was based off of some forum posts. These worked fine for small strings but when used on blocks of data larger than a few kilobytes it took an unacceptably long time.
#Initial XOR method from forums - http://www.ruby-forum.com/topic/175539
#0.627 Mb/s (All times are based on a single core of my development machine - i5 750 @ 2.67GHz)
def xor1(second)
  self.bytes.zip(second.bytes).map { |a,b| (a^b).chr}.join
end
At this point I started investigating writing a C extension to give the process a boost and eventually succeeded after a night of furious hacking. As you can see in the benchmarks below this was about a 2000x speedup and I decided to move on to other features. It wasn't until after the project was finished that I started wondering what sort of speedup I could get using a pure ruby implementation and if it would even be comparable to my naive first stab at a ruby C extension. So I started trying different methods out.

This next method avoids the bytes method and accesses each string char directly then converts them to their ordinal values performs the XOR and pushes it on to the string. More than a 2x speedup from the first implementation.
#1.589 Mb/s
def xor2(second)
  s = ""
  [self.size,second.size].max.times do |i|
    s << ((self[i] || 0).ord ^ (second[i] || 0).ord)
  end
  return s
end
Further refinement involves using the zip method to combine the strings as byte arrays resulting in some marginal speedups.
#1.881 Mb/s
def xor3(second)
  s = ""
  #s.force_encoding("ASCII-8BIT")
  [self.size,second.size].max.times.zip(self.each_byte.to_a,second.each_byte.to_a) do |i,a,b|
    s << (a ^ b).chr
  end
  return s
end
Enter the pack and unpack methods. Seemingly the quickest way to convert the string into byte arrays.
#2.613 Mb/s
def xor4(second)
  s = []
  self.unpack('C*').zip(second.unpack('C*')) {|a,b| s.push( (a||0) ^ (b||0) ) }
  return s.pack('C*')
end
I attempted the operation with unpack options larger than 1 byte. This works great for large data sets that have sizes evenly divisible by 8 bytes. The 0-7 bytes at the end will be ignored. Some extra code could probably be written to account for this. As the speedup is substantial and this represents the fastest ruby implementation I was able to produce.
#13.369 Mb/s
def xor5(second)
  s = []
  self.unpack('Q*').zip(second.unpack('Q*')) {|a,b| s.push( (a||0) ^ (b||0) ) }
  return s.pack('Q*')
end
And finally a more robust method that properly takes into account all sizes and differing lengths of strings. This implementation is essentially the same as xor4.
#2.605 Mb/s
def xor6(second)
  self.length > second.length ? (string1=self;string2=second) : (string2=self;string1=second)
  s = []
  string1.unpack('C*').zip(string2.unpack('C*')) {|a,b| s.push( (a||0) ^ (b||0) ) }
  return s.pack('C*')
end
In the end my fastest ruby implementation was still about 2 orders of magnitude slower than the C solution. I'm sure there's more room for performance improvements and I hope someone out there takes a look at this and finds a more optimal ruby only solution. Until then I'll likely start looking to C more often when I need to do some performance optimization. You can see the code used in this post as well as the C libraries used on github at https://github.com/pcorliss/Ruby-String-XOR-Optimizations

Note: Towards the end of preparing this post I came across the ruby gem fast_xor  which is based on C like my solution. However fast_xor stands out since it runs at 5x the speed my implementation does. It's included in the benchmarks below and you should probably implement it if you're considering doing any non-trivial XOR work in your ruby application. Otherwise xor6 should be suitable for most use cases.
pcorliss@hawaii:~/projects/ruby_benchmarks$ ruby benchmark.rb 
Ensuring parity calculation is correct.
xor1:1d9d0030fe31415e1fa9dd8b7b782315
xor2:1d9d0030fe31415e1fa9dd8b7b782315
xor3:1d9d0030fe31415e1fa9dd8b7b782315
xor4:1d9d0030fe31415e1fa9dd8b7b782315
xor5:1d9d0030fe31415e1fa9dd8b7b782315
xor6:1d9d0030fe31415e1fa9dd8b7b782315
xorc:1d9d0030fe31415e1fa9dd8b7b782315
xor!:1d9d0030fe31415e1fa9dd8b7b782315
Performing Benchmarks
      user     system      total        real
xor1 10 times: 79.040000   0.440000  79.480000 ( 79.689313)
xor2 10 times: 31.410000   0.000000  31.410000 ( 31.467753)
xor3 10 times: 26.280000   0.240000  26.520000 ( 26.583489)
xor4 10 times: 18.690000   0.400000  19.090000 ( 19.136069)
xor5 10 times:  3.720000   0.020000   3.740000 (  3.740085)
xor6 10 times: 18.650000   0.500000  19.150000 ( 19.191539)
xorc 10 times:  0.040000   0.000000   0.040000 (  0.041738) #My C implementation
xor! 10 times:  0.000000   0.000000   0.000000 (  0.011803) #fast_xor gem
xorc 1000 times:  4.370000   1.390000   5.760000 (  5.785931) #My C implementation
xor! 1000 times:  1.160000   0.000000   1.160000 (  1.170025) #fast_xor gem

December 27, 2010

Code Coverage and Ruby Speed - Monday

This week we'll be going in a slightly different direction. I'll be spending it going back and refactoring some previous projects to include test cases and do some refactoring, specifically the route 53 gem  which has seen some moderate popularity since it was released as evidenced by the number of followers on the github page as well as the number of downloads from rubygems.

https://rubygems.org/gems/route53 (254 downloads)
https://github.com/pcorliss/ruby_route_53 (25 watchers, 5 forks)

This is in addition to a post I wanted to write on ruby and the great lengths you have to go to sometimes to get reasonable speeds. Specifically I'll go in depth to the speed boost gained from writing a C extension to the LazyRaid project to perform XORs. See this post for the original mention of the problem.

The Future of 50projects

Over the last 5 months I've done a lot of work that I'm proud of. However the revenue and traffic has been far lower than even my most conservative estimates. I think I'm a decent software developer but I am not a marketer. At this point my finances are stable enough that I could continue doing 50projects for the entire year and then some but I just don't feel comfortable not collecting a paycheck. With that said I'll be starting a job search at the beginning of the new year. I've already put together a short of list of companies in the Chicago area I'll be pursuing. In addition I'm open to entertaining contract software development opportunities so please feel free to contact me if you know someone that is looking for a developer in the Chicago area or is open to telecommuting and/or light travel.

I'll post my resume shortly but my linkedin profile is a good approximation.

Until I accept a position I'll continue to do weekly projects and post the results on 50projects but the scope may be reduced as I go on interviews. Afterwards I may move to a more relaxed style of 2-4 week projects spread out over more than one year. But I fully intend to continue posting updates and releasing projects.

Festivus Greetings - Sunday

Those looking at the posting time will note that this post is title "Festivus Greetings - Sunday" when it is in fact Monday morning. I neglected to write the post yesterday. However the final version went out Saturday afternoon.

You can take a look at http://festivus.50projects.com/ Note: Taken down to save on costs

There are some issues with the webcam recording process that I've been unable to reproduce but others have run into. So please make sure to click the play button after you finish recording your message to ensure it looks okay. And when in doubt just reload the page and record again.

The backend on this turned out to be more complicated than I had hoped requiring the setup of a VPS server (Linode) to host a Red5 server. On the plus side I hadn't had the opportunity to work with Ruby enterprise edition or nginx before. Setup was a breeze after I got my bearings. I'm particularly impressed by REE's memory footprint for a single instance of my app (29Mb).

December 20, 2010

Festivus Greetings - Monday

Prior to moving to Chicago I threw the wildest, most extravagant Festivus parties every year during the holiday season [citation needed]. We deep fried turkeys, had a toasty fire going, served copious amounts of alcohol and adhered to the strict traditions of a made up holiday. There was a poll which pictures were taken with, an airing of grievances and Festivus wasn't over until the host was pinned. Truly a marvelous holiday.

This year I'll be away from my friends in a strange city but I still want the Festivus cheer to be a part of my life. So this week's project will center around developing a site and backend components to allow people to post their Festivus greetings and grievances.

Project Summary: Website to allow users to view and post their festivus greetings.

Features:

  • Greeting Playback
  • Built in browser based webcam recording
  • RTMP stream handling to back browser based webcam recording
  • Youtube and Vimeo integration

December 19, 2010

Subject Content Aggregator - Sunday

Initially this project was designed to be a method of attracting search engine traffic by aggregating popular content but I'm doubtful that it will attract enough traffic to generate significant revenue. Despite that it's up and running and pulling in content from multiple sources and for multiple subjects.

I'm not excited about the design. It definitely lacks any flare. But as far as basic features it does what it's supposed to. RSS aggregation, display of popular articles only, multiple subjects, etc..

You can check it out at http://aggie.50projects.com

December 13, 2010

Subject Content Aggregator - Monday

Like a lot of people I have a handful of sites I like to visit on a daily basis. I had tried Google reader in the past and it allowed me to stay up to date on all of my favorite subjects but I found myself avoiding it because it was too much information. I just want a light taste of the subject, I don't have the time or the patience to become an authority on the subject.

That leads into this week's project, think of it like Google news with less customization and focused on specific topics. To start with I'll be selecting just two topics (video games & possibly sports) but building in functionality to support a quick build out of multiple topics in the future. Content will automatically be aggregated from multiple sources via RSS feeds, parsed, duplicate articles combined and posted to the site. The idea is to make this a completely automated process with zero-editorial oversight. Ideally this will lead to a way for people to get good content on specific subjects without having to rely on the delay of user moderated or editorially based content.

Project Summary: Customizable Content Aggregator and Presenter

Features
  • RSS feed reader/parser/poster
  • Text analysis for article relatedness
  • Potential jekyll integration for speedy page generation and low overhead
  • Multiple topics support allowing instant spinup of other topics
  • Backend dashboard for maintenance

December 10, 2010

Amazon Route 53 Interface - Friday

After a few days of tweaking I'm happy to say that I've ferreted out all the obvious bugs and things are looking good. The ruby interface to Amazon's Route 53 can officially be called "Released". Complete with a command line interface as well as a module for inclusion in other ruby projects. All packaged up nice and neat in my first gem.

You can find the source and documentation at https://github.com/pcorliss/ruby_route_53

Bug reports should either go to me or to the Issues page on github.

If you already have the gem installed you'll want to run

sudo gem update route53

Otherwise

sudo gem install route53

December 08, 2010

Amazon Route 53 Interface - Wednesday

An early prototype of the Ruby interface for Amazon's Route 53 is available on github and as the route53 gem. You can install the gem by typing the following.

gem install route53

There isn't any documentation and the command line interface isn't done yet but this should get folks started that want to create a zone or two along with some simple records.

https://github.com/pcorliss/ruby_route_53
https://rubygems.org/gems/route53

Edit: No longer a prototype http://blog.50projects.com/2010/12/amazon-route-53-interface-friday.html

December 06, 2010

Amazon Route 53 Interface - Monday

Last night just as I was struggling to figure out which project I would work on this week I saw a post on Hacker News regarding Amazon's latest web services offering. Route 53 offers programmatic control of Amazon's globally based DNS service designed for a high volume of queries. At $1/domain/month with additional charges for high query volume it's questionable whether this service will take off for the general consumer market given that most domain registrars provide DNS for free already. However in the professional market where the needs for highly configurable, low TTL, low latency DNS services is necessary Route 53 could be a contender.

As the service was just released it doesn't have a generally usable front end. The getting started documentation has the user editing XML files to begin using the service. I'm uncertain if this is a deliberate attempt by Amazon to get the developer community to build the tools to interact with the service or just a function of the product being in beta mode still. Regardless I'm taking the bait.

Project Summary: Create an interface for Route 53

Features:
  • Ruby Gem based
  • Command Line Interface
  • Easy to Use Programmatic Control
  • Simple web front end
Edit: Early prototype of a ruby library for Amazon's Route 53 is now available

Edit 2: No longer a prototype http://blog.50projects.com/2010/12/amazon-route-53-interface-friday.html

December 04, 2010

Those Pesky Body Scanners - Saturday

Friday was a bit of a blur as I tried to tie up all the loose ends with a project that crop up towards the end. Small bugs and minor features sneak in at the last minute and as you try to make it perfect the time slips away. I started at 9am yesterday and finished around 7pm with only a quick break for lunch. All the while I was only 30 minutes away from releasing.

The good news is that everything did indeed get sown up last night. And the Airport Security Index is now up and running. Please feel free to add new information and confirm or deny information already present. The interface is pretty simple, or at least I hope so, so you shouldn't have much trouble navigating the site.

The premise is simple. Each airport is listed with all reported security features. As folks use the site new security features will be listed and users will have the opportunity to either confirm or deny the presence of a body scanner. Additionally users can add missing information. There are some gaps right now as I could only get TSA provided information. But I'm hoping with a sufficient user base things will fill in more.

Link: http://airdex.50projects.com/

Challenges: Over all this project seems to have gone over pretty well. A slow start initially due to some technical challenges working through rails (what else is new) but Thursday and Friday were incredibly productive.