IronRuby

What’s Wrong With This Picture?

4 commentsWritten on October 22nd, 2010 by
Categories: IronRuby, What's Wrong With This Picture

For once, absolutely nothing.

(it's alpha, so it's not smooth sailing yet, but it's great to see nonetheless)

Performance Of NHibernate With Ruby Objects Compared To Traditional C# Objects

3 commentsWritten on October 19th, 2010 by
Categories: IronRuby, NHibernate, Performance, Ruby

I recently showed how you can use NHibernate to persist and query Ruby objects through IronRuby. We've continued the experiment (though we've already done some big optimizations in the code based on the first results of these tests) and we recently had to decide whether or not the performance difference between using NHibernate with regular static C# code and using it with dynamic Ruby objects was acceptable. So we ran a set of tests, and compared all of the numbers. Note that we don't claim that these benchmarks are scientifically correct in any way, but we do think they give us a good idea on what we can reasonably expect. I want to share the results with you, and would appreciate any feedback you guys have on this... particularly on whether or not we missed something obvious in our tests or whether or not we should trust these numbers. After all, we're not professional benchmarkers so our approach might very well just suck :)

We have a scenario which consists of 15 'actions'. For these actions, we use some tables from the Chinook database, basically just Artist/Album/Track/Genre/MediaType. The actions are the following:

  1. Retrieve single track without joins, and access each non-reference property
  2. Retrieve single track with joins, and access all properties, including references
  3. Retrieve single track without joins, and access all properties, including references (triggers lazy-loading)
  4. Create and persist object graph: one artist with two albums with 13 tracks each
  5. Retrieve created artist from nr 4, add a new album with another 13 tracks, change the title of the first album from nr 4, and remove the second album from nr 4 including its tracks
  6. Retrieve created artist from nr 4 and delete its entire graph
  7. Create a single track
  8. Retrieve single track from step 7 and update it
  9. Retrieve single track from step 7 and update the name of one of its referenced properties
  10. Retrieve single track from step 7 and change one of the reference properties so it references a different instance
  11. Delete the track from step 7
  12. Retrieve 100 tracks and access each non-reference property
  13. Retrieve 200 tracks and access each non-reference property
  14. Retrieve 100 tracks without joins and access all properties, including references (triggers lazy-loading)
  15. Retrieve 100 tracks with joins and access all properties, including references

Note: when i say we access reference properties to trigger lazy loading, i mean that we access a non-id property of the referenced property to make sure it indeed hits the database.

The scenario is ran 500 times with regular C# objects, and 500 times with Ruby objects. We keep track of the average time of each action in the scenario, as well as the total duration of the scenario. Also, keep in mind that we ran these tests on a local database.

The following graph shows the average duration of each action in milliseconds on the Y axis, and the number of the action on the X axis:

(you can click on the graph to watch it in its full size)

Before i'll discuss these results, i'd also like to show the following graph which shows the average difference in milliseconds between the static and the dynamic execution of each action:

Two actions immediately stand out: the last two which both deal with fetching a set of items and accessing all of their properties. They're both about 6ms slower than their static counterparts, which is a performance penalty of 71% for action 14, and 87% for action 15. That deals with a part of code that we can't really optimize any more. Well, it probably is possible but we've already done a lot of work on that, and this is the best we can come up with so far.

Now, those 2 actions are things we avoid as much as possible in real code anyway, so maybe they aren't that big of an issue. The other 2 actions where there is a noticable difference (though it actually means an increase in average execution time of 1.1ms using a local database) is the creation and persistance of an object graph (step 4), and the retrieval/modification/persistence of that same graph (step 5). Most other actions don't have a noticeable difference, and in some cases the dynamic version is actually faster than the static one, no doubt because NHibernate has in some cases less work to do when using the Map EntityMode (which we rely on for the dynamic stuff) compared to the Poco EntityMode.

We also wanted to see whether the performance difference would get worse when spreading the workload evenly over a set of threads, or even a 'pool' of IronRuby engines. I was pretty happy to see that it didn't really lead to a noticeable difference.

The following graph shows the average duration of the entire scenario in a couple of different situations:

I do have to mention that the numbers shown in this graph aren't averages, but the result from running the scenario once in each situation. We did however ran the scenarios in each situation more than once, and while we didn't list the averages, the numbers are representative of each testrun... we didn't see any really noticeable differences over multiple runs. The percentage difference for each situation is shown in this graph:

As you can see, the performance penalty of the entire scenario in each situation varies between 15% and 26%.

Now, considering the fact that we prefer to avoid loading 'large' sets of data through NHibernate into entities (we prefer to use projections instead for that) we wanted to see what the difference would be for the entire duration of the scenario in each situation, without the final 4 actions. Basically, just the typical CRUD scenarios:

Now the difference varies between 6% and 15%.

Now, suppose that we have a compelling reason to actually go ahead with using this approach (we do actually, but i'm not gonna get into that here), do you think we can trust these numbers? Is there anything else we're missing? Are we complete idiots for testing the performance difference like this? Do you have any feedback whatsoever? Then please leave a comment :)

Monkey Patching FTW!

2 commentsWritten on October 4th, 2010 by
Categories: IronRuby, Ruby

Today, i had to get a CI build of an IronRuby project that a coworker and me have been working on up and running. We have a TeamCity server and i'm pretty familiar with it, at least as far as our .NET projects are concerned. But this is the first project we're using IronRuby on, and for now it's exclusively Ruby code that has to run on IronRuby. Our requirements of the CI build are very simple: check out the latest version of the code from Subversion, run the tests and make sure we can consult the test results from the TeamCity web interface. That's it. How hard can that be, right?

The thing is... we're not using an official IronRuby version. I basically get the latest code from IronRuby's GitHub repository from time to time, build it, and we use that. I've included all of the necessary files into our subversion repository so we can just refer to the correct IronRuby version with relative paths. And no, it's not because we're trying to be cool or hardcore, it's because we depend on a fix that has been implemented in IronRuby already but that isn't present in one of the releases. So my coworker sent me a link from the TeamCity documentation that mentioned that you could just use a Rakefile with IronRuby. Easy peasy! Well, except for the fact that it would require me to install the IronRuby build that we happen to be using on the build agents, and that i'd have to update it whenever i update the IronRuby binaries that we're using. Not exactly an approach i'd prefer.

So i was already thinking along the lines of "great, we're gonna have to write yet another custom test runner to report the test results back to TeamCity". We did it back when nobody cared about writing tests for Silverlight code, so i guess we could do it again. But i just sort of looked up to it. And then my coworker said "why not just monkey patch the test runner so it outputs the results in the format that TeamCity can understand?". And he was right. There's no reason whatsoever not to use a monkey patch to get out of this bind.

The final result is a pretty minimal amount of code that didn't take long to write which gets the results we need. Granted, i lost some time because at first i was monkey patching Test::Unit's console TestRunner only to find out that it's not really being used anymore if you're on Ruby 1.9... it's been replaced with MiniTest, which unfortunately (yet understandably) trades clean code for runtime performance. If Test::Unit's console TestRunner was used, the final result would've been less than 20 lines of code in total. Now, it's a bit more but it's still pretty minimal.

First of all, it's important to know the format that TeamCity can understand from your custom build output. You can find all you need to know about that here. Once you know the expected format, the solution is actually pretty easy: change the behavior of the testrunner at runtime so that it formats the output in a way that TeamCity can do something with it instead of its regular output. Turns out i could limit my monkey patch to just one of MiniTest's classes, that being the MiniTest::Unit class. First of all, we need to add some helper methods that we can use to take care of some of TeamCity's formatting requirements:

  def tc_output(string)
    tc_string = "##teamcity[#{string}]"
    puts tc_string
    tc_string
  end
     
  def tc_escape(string)
    string
      .gsub("|", "||")
      .gsub("'", "|'")
      .gsub("\n", "|n")
      .gsub("\r", "|r")
      .gsub("]", "|]") 
  end

With those methods added to the MiniTest::Unit class, we can now modify the behavior of 2 methods of this class to get the result that we want and need. First up, is the puke method, and no, i'm not joking... the method is actually called 'puke':

  def puke(klass, method, error)
    error = case error
      when MiniTest::Skip then
        @skips += 1
        tc_output "testIgnored name='#{method}' message='test ignored'"
      when MiniTest::Assertion then
        @failures += 1
        trace = MiniTest::filter_backtrace(error.backtrace).join("\n")
        tc_output "testFailed name='#{method}' message='#{tc_escape(error.message)}' details='#{tc_escape(trace)}'"
      else
        @errors += 1
        trace = MiniTest::filter_backtrace(error.backtrace).join("\n")
        tc_output "testFailed name='#{method}' message='#{tc_escape(error.message)}' details='#{tc_escape(trace)}'"
    end
      
    error[0,1]  
  end

This method is called by MiniTest whenever a test has failed... ignoring (or skipping in the MiniTest terminology) a test is a 'failure' (and i can't really argue with that). And obviously, both assertion failures or runtime exceptions are considered to be test failures as well. In either of these 3 cases, the puke method is called and it is supposed to output something to the user to notify him/her of the problems. So i basically just took the existing code, and modified it so its output would be in the format that TeamCity can work with. Next up is the run_test_suites method, which is responsible for, you guessed it, running the tests in the various test suites.

  def run_test_suites(filter=/./)
    @test_count, @assertion_count = 0, 0
    old_sync, @@out.sync = @@out.sync, true if @@out.respond_to? :sync=
    TestCase.test_suites.each do |suite|
      tc_output "testSuiteStarted name='#{suite}'"
      suite.test_methods.grep(filter).each do |test|
        inst = suite.new test
        inst._assertions = 0
        tc_output "testStarted name='#{test}'"
        @start_time = Time.now
        result = inst.run(self)
        duration = "%f" % ((Time.now - @start_time)*1000)
        tc_output "testFinished name='#{test}' duration='#{duration}'"
        @test_count += 1
        @assertion_count += inst._assertions
      end
    end
    @@out.sync = old_sync if @@out.respond_to? :sync=
    [@test_count, @assertion_count]
  end

Again, i just took the existing code and changed its output so that TeamCity can work with it.

And the final result is this:

As you can see, build #2 didn't give you any feedback on the tests, even though they were being executed properly. Build #3 reported 2 failing tests, which were my temporary test cases to see how failed assertions or actual errors would be reported by TeamCity. Build #4 reports that all tests passed. In case you're interested, our 'build script' looks like this:

..\ironruby\bin\dotnet\ir -w tests\suite.rb

And that's it... pretty simple, no?

That just goes to show that while monkey patching is considered by a lot of people to be 'evil', it certainly has its benefits from time to time. I'm not saying you should use it as much as possible. But when it makes sense to do so, and if you're aware of the downsides and the pitfalls, then there's nothing wrong with it at all. Though it does require a language that treats you like an adult and expects you to know what you're doing ;)

Mad Scientist At Work

2 commentsWritten on September 8th, 2010 by
Categories: IronRuby, NHibernate, Ruby

Just wanted to show some code, and yes, it actually works :)

    using (var session = sessionFactory.OpenSession())
    {
        var artist = ruby.ObjectFactory.CreateArtist();
        artist.Name = "some name";
        session.Save("Artist", artist);
        session.Flush();
        // and this actually prints out the id of the artist (in this case, it's defined as an identity in sql server)
        Console.WriteLine(artist.ArtistId());
    }

and

    using (var session = sessionFactory.OpenSession())
    {
        var albumId = 1;
        var artistId = 1;

        var album = ruby.ObjectFactory.create_from_nhibernate_hash(session.Get("Album", albumId));
        // the following line does NOT issue a select statement
        Console.WriteLine(album.Artist().ArtistId());
        // but this one obviously will trigger the lazy loading of the Artist instance
        Console.WriteLine(album.Artist().Name());

        var realArtist = ruby.ObjectFactory.create_from_nhibernate_hash(session.Get("Artist", artistId));

        album.Title = album.Title() + " 2";
        realArtist.Name = realArtist.Name() + " 2";

        if (album.Artist() == realArtist && album.Artist().Name() == realArtist.Name())
        {
            // this actually persists the changes
            session.Flush();
        }
        else
        {
            throw new InvalidOperationException("The universe should collapse");
        }
    }

I can't get property syntax working properly (as in: without requiring the parentheses) when accessing mapped properties due to an issue with IronRuby that needs to be fixed first, but other than that i'm pretty happy with how this works :)

Full details will be posted once i've taken care of a few more things ;)

Using Ruby Classes In C# With IronRuby

9 commentsWritten on September 6th, 2010 by
Categories: IronRuby, Ruby

I wanted to see how easy or difficult it would be to use your own Ruby classes from C# through IronRuby. Turns out it's pretty easy to do so.

Suppose we have the following simple Ruby classes:

class Customer
  attr_reader :name
  attr_reader :email
  
  def initialize(name, email)
    @name = name
    @email = email
  end
end

class Product
  attr_reader :name
  attr_reader :price

  def initialize(name, price)
    @name = name
    @price = price
  end
end

class OrderItem
  attr_reader :product
  attr_reader :count

  def initialize(product, count)
    @product = product
    @count = count
  end
end

class Order
  attr_reader :customer
  attr_reader :date
  attr_reader :discount
  attr_reader :items
  
  def initialize(customer, discount, date)
    @customer = customer
    @discount = discount
    @date = date
    @items = []
  end
  
  def add_item(item)
    @items << item
  end
end

Each of those classes is located in its own file, but i've listed all of the code together here. Now, how hard or easy would it be to say, create an Order instance in C#?

It turns out to be pretty easy. It's pretty easy to start up a Ruby engine in .NET and have it execute some Ruby files. Now, i don't want to tell it to execute each file, so i create a bootstrap.rb file which contains the following code:

load 'product.rb'
load 'customer.rb'
load 'orderitem.rb'
load 'order.rb'

Obviously, this just loads each entity's file into the current scope.

Now, i can just do this in C#:

            var engine = Ruby.CreateEngine();
            engine.ExecuteFile("bootstrap.rb");
            dynamic ruby = engine.Runtime.Globals;

            dynamic customer = ruby.Customer.@new("Davy Brion", "davy@gmail.com");
            dynamic product1 = ruby.Product.@new("product1", 50);
            dynamic product2 = ruby.Product.@new("product2", 60);
            dynamic order = ruby.Order.@new(customer, null, DateTime.Now);

            order.add_item(ruby.OrderItem.@new(product1, 5));
            order.add_item(ruby.OrderItem.@new(product2, 5));

            var total = 0;
            foreach (dynamic item in order.items)
            {
                total += item.count * item.product.price;
            }

            Console.WriteLine("order total: " + total);
            Console.WriteLine();
            Console.WriteLine(order.inspect());

And the output of that is this:

order total: 550

#<Order:0x000005c @customer=#<Customer:0x0000056 @name='Davy Brion', @email='davy@gmail.com'>, @discount=nil, @date=9/5/2010 22:36:14, @items=[#<OrderItem:0x000005e @product=#<Product:0x0000058 @name='product1', @price=50>, @count=5>, #<OrderItem:0x0000060 @product=#<Product:0x000005a @name='product2', @price=60>, @count=5>]>

Not sure what you think of that, but i thought it was pretty impressive. I'd hoped that this would be possible, but i wasn't sure since most of the examples you see about IronRuby seem to be focused on using .NET types from Ruby code that is interpreted by IronRuby's interpreter. But given the flexibility that you have in Ruby when it comes to designing classes, i'm much more interested in using Ruby classes from C# code instead of the other way around.

Let's go over some parts of the code...

            var engine = Ruby.CreateEngine();
            engine.ExecuteFile("bootstrap.rb");
            dynamic ruby = engine.Runtime.Globals;

This is all you need to do to start the Ruby engine (no idea why they call it that but whatever) and execute our bootstrap.rb file so that our classes are defined. We then have access to IronRuby's top-level binding through the engine.Runtime.Globals property.

So now we can simply create instances of these classes like this:

            dynamic customer = ruby.Customer.@new("Davy Brion", "davy@gmail.com");
            dynamic product1 = ruby.Product.@new("product1", 50);
            dynamic product2 = ruby.Product.@new("product2", 60);
            dynamic order = ruby.Order.@new(customer, null, DateTime.Now);

It's too bad that we have to escape the 'new' method because the C# compiler should be capable of figuring out that we aren't using the new operator there. But other than that, i'm pretty happy with how this works.

            order.add_item(ruby.OrderItem.@new(product1, 5));
            order.add_item(ruby.OrderItem.@new(product2, 5));

As you can see, calling methods on the instances of our Ruby classes looks normal as well, except for the fact that those classes use the typical Ruby naming conventions instead of offering a typical .NET-looking AddItem method. We'll fix that later though ;)

            var total = 0;
            foreach (dynamic item in order.items)
            {
                total += item.count * item.product.price;
            }

Now this is actually cooler than it might appear on first sight. First of all, notice the lack of parentheses when we call order.items, item.count and item.product.price. Big deal, you use properties all the time right? Well, Ruby doesn't have properties... it just has methods and due to some of its rules you can write this in Ruby:

  count = item.count
  item.count = 5

Which is actually only syntactical sugar for what it really is:

  count = item.count()
  item.count=(5) # the method name actually is 'count='

The fact that using these methods as if they are properties in C# is a nice touch, though it only works for accessor methods which were defined with the attr_reader, attr_accessor and attr_writer methods in your Ruby classes. If you defined your own accessor methods, you will have to use parentheses when you call them in C#.

The other thing that i find pretty cool about that piece of code is that you can use the foreach statement to loop through the return value of the order.items method, which is a Ruby array. Not sure whether IronRuby implicitly wraps Ruby arrays as IEnumerables or if it does that with all Ruby types which mix in the Enumerable module, but whatever it is, it's cool.

All in all, it's pretty nice that we can easily create and use these instances of classes that we defined in Ruby. But i'm a big fan of sticking to the accepted naming guidelines for each language. In Ruby, each method is lowercased and optionally uses underscores instead of the capitalized pascal cased method names that we typically use in C#. And when you use CLR types in Ruby code that is running in IronRuby, you can indeed stick to Ruby's naming conventions and IronRuby will automatically 'translate' method calls like write_line to WriteLine. It would be cool if, for instance, we could do order.AddItem in the example above and that it would just be 'translated' to order.add_item. And of course, if we could use capitalized versions of the accessor methods then it would look like pretty typical .NET code apart from the call to the @new method.

With Ruby code, pretty much everything is possible so it shouldn't be a surprise that we can easily 'fix' the naming convention issue. Keep in mind though that the approach i'm going to show is quite crude, and there most likely is a better way that i haven't thought of yet. I just added the following code at the bottom of the bootstrap.rb file:

if defined? IronRuby
  def add_dotnet_friendly_method_aliases_for(klass)
    klass.public_instance_methods.each do |method|
      klass.instance_eval do
        dotnet_friendly_name = IronRuby::Clr::Name.unmangle(method)  
        alias_method dotnet_friendly_name, method unless dotnet_friendly_name.nil? # in this case, it's already a dotnet friendly name
      end
    end
  end

  add_dotnet_friendly_method_aliases_for Product
  add_dotnet_friendly_method_aliases_for Customer
  add_dotnet_friendly_method_aliases_for OrderItem
  add_dotnet_friendly_method_aliases_for Order
end

Since we only need to do this if we're running in IronRuby, we first check whether the IronRuby constant is defined. If it isn't there's no point in adding the .NET-friendly method aliases. If we are running in IronRuby, we just add a method alias for each public instance method of our 4 classes. Again, it's a crude solution, but it does enable us to write the following C# code:

            var engine = Ruby.CreateEngine();
            engine.ExecuteFile("bootstrap.rb");
            dynamic ruby = engine.Runtime.Globals;

            dynamic customer = ruby.Customer.@new("Davy Brion", "davy@gmail.com");
            dynamic product1 = ruby.Product.@new("product1", 50);
            dynamic product2 = ruby.Product.@new("product2", 60);
            dynamic order = ruby.Order.@new(customer, null, DateTime.Now);

            order.AddItem(ruby.OrderItem.@new(product1, 5));
            order.AddItem(ruby.OrderItem.@new(product2, 5));

            var total = 0;
            foreach (dynamic item in order.Items)
            {
                total += item.Count * item.Product.Price;
            }

            Console.WriteLine("order total: " + total);
            Console.WriteLine();
            Console.WriteLine(order.Inspect());

And it works just like you'd expect it to. So far, i'm pretty happy, and very impressed with IronRuby :)