Institute for Personal Robots in Education Blog

Experiments in Dynamic Dispatch

In implementing a language like Scheme in a language like C#, you might want to write code like this:

public class Test {
    public static object multiply(object o1, object o2) {
	return o1 * o2;
    }
    public static void Main() {
	System.Console.WriteLine(multiply(1, 3.0));
    }
}

That is, you'd like to multiple an int times a double and have the system's type system figure out what to do. However, C# 3.0 and earlier didn't support such "dynamic dispatch". If you try to compile this with Mono:

mcs Test.cs

you'd get the error:

Test.cs(3,19): error CS0019: Operator `*' cannot be applied to operands of type `object' and `object'

But C# 4.0 now supports dynamic dispatch:

public class Test {
    public static dynamic multiply(dynamic o1, dynamic o2) {
	return o1 * o2;
    }
    public static void Main() {
	System.Console.WriteLine(multiply(1, 3.0));
    }
}

You can compile that with Mono:

dmcs Test.cs

and run:

mono Test.exe (or just ./Test.exe)

to get the output "3.0".

HOWEVER, this isn't going to run nearly as fast as code with types. In fact, I was a bit surprised at how slow it runs:

public class Test {
    public static double currentTime () {
	System.TimeSpan t = System.DateTime.UtcNow - new System.DateTime (1970, 1, 1);
	return t.TotalSeconds;
    }

    public static dynamic multiply(dynamic o1, dynamic o2) {
	return o1 * o2;
    }
    public static object multiplyWithCast(object o1, object o2) {
	if (o1 is double) {
	    if (o2 is int) {
		return (double)o1 * (int)o2;
	    } else if (o2 is double) {
		return (double)o1 * (double)o2;
	    } else {
		throw new System.Exception(System.String.Format("unknown type: {0}", o2));
	    }
	} else if (o1 is int) {
	    if (o2 is int) {
		return (int)o1 * (int)o2;
	    } else if (o2 is double) {
		return (int)o1 * (double)o2;
	    } else {
		throw new System.Exception(System.String.Format("unknown type: {0}", o2));
	    }
	} else {
	    throw new System.Exception(System.String.Format("unknown type: {0}", o1));
	}
    }
    public static double multiplyWithTypes(int o1, double o2) {
	return o1 * o2;
    }

    public static void Main() {
	double start = currentTime();
	for (int i = 0; i < 10000; i++) {
	    multiply(1, 3.0);
	}
	double end = currentTime();
	System.Console.WriteLine("dynamic: {0}", end - start);

	start = currentTime();
	for (int i = 0; i < 10000; i++) {
	    multiplyWithCast(1, 3.0);
	}
	end = currentTime();
	System.Console.WriteLine("cast   : {0}", end - start);

	start = currentTime();
	for (int i = 0; i < 10000; i++) {
	    multiplyWithTypes(1, 3.0);
	}
	end = currentTime();
	System.Console.WriteLine("types  : {0}", end - start);
    }
}

$ ./Test.exe 
dynamic: 0.152981996536255
cast   : 0.00215697288513184
types  : 0.00006890296936035

That is, dynamic is about twice as slow as casting, and 100,000 times slower than with types.

But all is not lost! You can actually mix typed with dynamic versions, like so:

    public static double multiply(int o1, double o2) {
	return (o1 * o2);
    }
    public static dynamic multiply(dynamic o1, dynamic o2) {
	return (o1 * o2);
    }

to get it so that it works for all types, but really fast for the known/specified ones. Perhaps this will help in writing Scheme code.

Post new comment

  • Lines and paragraphs break automatically.
  • Allowed HTML tags: <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
More information about formatting options