Thursday, November 15, 2012

Optimally Typing Numeric Code

Having consistent runtime types will have a performance benefit with Dart. Read on to learn how to write Dart that is flexible with tool side types while keeping consistent runtime types for optimal performance.

I like to think of Dart as having two separate type systems. The first type system is expressed when you write code at the syntax level and is used by tools like the Dart Editor to check for errors, the second type system is expressed at runtime and is known to the Dart VM. The VM uses this run-time type system to optimize code that is executed. If the run-time type of a parameter passed to a function changes over time the code will be de-optimized. For high performance code you must keep the run-time types consistent.

When writing a Dart class, classify the types into: input, computation, and output types. Input types are those passed as arguments to the class’ constructor or instance methods. Computation types are those used at execution where the work is done. Output types are returned from instance methods to the calling code.

When I first wrote Dart Vector Math it used the num type for input, computation, and output types for example:

This would allow a developer to write:

At runtime a has instances of double and b has instances of int. Any optimizations attempted by the Dart VM will be undone when switching between the two instances of vec3.

After benchmarking I saw that there was a large performance loss from allowing for both int and double instances inside my vector class. So I switched my class to look like:

Which forces all input, computation, and output types to be doubles. Performance problem solved, but, if a developer casually wrote:

Checked mode would trigger an error and the editor would nag because “1” and “3” are not instances of double.

So, what’s the solution?

  1. Member variables should have the ideal computation type.
  2. Inputs should use the num type.
  3. Inputs should be explicitly converted to the computation type, for example, call .toDouble()
  4. Outputs should match the computation type.

Or in code:

The example used throughout this article focused on doubles but the same is true if the computation type is an integer.