Runtime Optimizations in X#

Some of the many items in our TODO list for Vulcan.NET had to do with optimizing the runtime and adding some missing functionality (like passing USUALs by reference to CLIPPER methods) to it. We had already implemented some, like improving performance of the late binding system, but for obvious reasons we were not able to make any further improvements in Vulcan.NET. But now, with the fresh start of XSharp, it is a great opportunity to make things right and fast from the beginning. In this article we will discuss optimizations planned or already implemented on the USUAL, FLOAT and ARRAY data types, more about other types and parts of the runtime will follow in future articles.

The problem with the USUAL type

In Vulcan.NET, the VO-compatible USUAL type is defined as a structure (value type), which makes sense, as it is easier this way to emulate the semantics of the USUAL type, the same way as it behaves in VO. Structures are supposed to be lightweight, compact data types, usually a few bytes long, in order to achieve good performance when copying values from one to another, or passing them as parameters to methods etc. Examples of very common structures used in the system classes are System.Drawing.Point (8 bytes long) and Rectangle (16 bytes long).

The problem with the way the USUAL type is implemented in Vulcan.NET, is that it’s much longer than that. It is easy to check its size, with a simple line of code:

? SizeOf(USUAL)

This reveals that the USUAL type is 28 bytes long, which is a bit too long and is hurting performance, especially in tight loops. It also causes high memory consumption, especially considering the fact that the elements of the ARRAY data type are of USUAL type, and usage of ARRAYs is extremely common in VO-style applications. In addition, the ARRAY type internally uses an ArrayList (and not a List<USUAL>) for holding its elements, which further contributes to a larger memory footprint. Because of the two above reasons combined, an ARRAY of one million elements (even if they are all NIL) in Vulcan.NET consumes 40 megabytes of memory!

Without getting into too much technical detail, the reason why Vulcan’s USUAL is 28 bytes long, is that, as can be easily seen with Reflector or any similar tool, it consists of the following instance fields:

FIELD TYPE SIZE
_IsByRef System.Boolean 4 bytes
_usualType UsualType (INT32 ENUM) 4 bytes
_value System.Object 4 bytes
_valueNoGC NonGCData (STRUCTURE) 16 bytes

Which make for a total length of 28 bytes. (Note: those size values are accurate when building the runtime in 32bit mode, which is the only mode in which the VO-compatible runtime types are designed to be used with. For XSharp, it is not yet decided if it will be possible to use those types also in 64bit/AnyCPU mode, but in the rest of the article we will always assume 32bits, for direct comparison with the Vulcan.NET implementation).

The NonGCData type is another structure, a union that holds non-collectable data types that the USUAL type directly supports, like INTs, LOGICs, FLOATs etc. The reason why this structure is 16 bytes long, it is that one of its members is of the FLOAT type, which is another structure that consists of the following fields:

FIELD TYPE SIZE
_decimals System.Int32 4 bytes
_digits System.Int32 4 bytes
_value System.Double 8 bytes

where “_value” holds the actual value of the FLOAT, while the other two fields hold formatting information.

Compacting the USUAL type

Fortunately, it is possible to implement the USUAL type in a similar, but more compact way. In XSharp, we have used a similar field layout, but the type of the _IsByRef and _usualType fields have been changed to BYTE, saving 6 bytes from Vulcan’s implementation, while keeping the same functionality. Furthermore, the NonGCData union does not include a complete FLOAT field, but only a System.Double field for storing only the actual numeric value instead, reducing its size from 16 to 8 bytes. The formatting information is being stored inside the USUAL type instead, in two new BYTE fields.

The following table shows the new layout of the USUAL class, as is currently implemented in the XSharp runtime (please note that the actual field names in our current implementation are different to the ones listed below, but in this article we will use Vulcan.NET – like names, again for direct comparison):

FIELD TYPE SIZE
_IsByRef System.Byte 1 byte
_usualType UsualType (BYTE ENUM) 1 byte
_value System.Object 4 bytes
_valueNoGC NonGCData (STRUCTURE) 8 bytes
_float_digits System.Byte 1 byte
_float_decimals System.Byte 1 byte

As can be seen from the above table, the size of the USUAL data type has now been reduced from 28 to 16 bytes. The only disadvantage of this implementation is that it requires additional code to be written in the operator methods that handle conversions between USUAL and FLOAT types, but our tests so far have shown that the speed increase because of the smaller structure size outweighs (in many cases by far) the small penalty of that additional code. Of course we will have more definitive results when we have completed writing the VO-compatible XSharp runtime, so we can test it against real existing applications, but this concept is already looking very promising.

Improvements to the ARRAY type

Having reduced the size of the USUAL type allows us to also improve the ARRAY type as well (since the elements of ARRAYs are USUALs). When we added support for (consuming) generics in the Vulcan.NET compiler, we also attempted to change the code of the ARRAY type in the runtime, so that it internally uses a List<USUAL> instead of an ArrayList. In our tests, this had produced a considerable general performance increase and reduced memory footprint, but also had a disadvantage: it dropped performance of the AIns() function, especially with large arrays, as inserting items would require large amounts of data to be moved in memory (28 bytes x amount of elements to be moved). Fearing of possibly causing performance issues in some applications, we had decided to freeze this change.

A smaller size of the USUAL type reduces the side-effect mentioned above, so now in the XSharp runtime we are indeed using a generic List<USUAL> for storing elements in our ARRAY type. This has not only improved performance, but it has also significantly reduced memory consumption, as a one million element ARRAY in XSharp consumes 16 megabytes of memory, which is a vast improvement over the 40 megabytes that it does in Vulcan.NET!

Further improvements to runtime types

Another improvement we have already implemented (although not as important as the ones mentioned earlier), was to reduce the size of the FLOAT structure in XSharp from 16 bytes to 12, in a similar manner to the way the size of the USUAL type was reduced.

We are also experimenting with more optimizations and improvements as well. For example, we are researching ways to best implement passing parameters by reference to CLIPPER methods (this will also utilize the _IsByRef filed of the USUAL class that is currently not used), improve the implementation of the DATE and SYMBOL types, add better support in the USUAL type for handling System.Decimal values and much more. In a future article we will discuss some more of those planned improvements.

 If you want to see an example of some of the size and speed tests, look in the downloads section of this website


5 comments

  • Otto and Leonid,

    Glad to hear you liked the article. Yes, after a long time, enthusiasm is back!

    Chris


    [quote name=&quot;Otto Christiaanse&quot;]Nice to hear what your ideas, backlog and progress is.
    Nice to hear your enthusiasm![/quote]
  • Looks like nice optimizations. I attach mostly useless
    comparsion with Harbour, compiler doesn't support static-typing so far.
    (http://www.fki.pl/hb/xsharp_cmp.zip compiled with -gc3 optim. flag)

    Harbour Build Info
    Version: Harbour 3.2.0dev (r1504082220)
    Compiler: Microsoft Visual C++ 13.10.3077 (32-bit)
    Built on: Apr 16 2015 21:14:53
    Build options: (C++ mode) (Clipper 5.3b) (Clipper 5.x undoc)

    Size of USUAL 24
    Memory for 1M element ARRAY 24084480 bytes
    Testing Harbour USUAL & Float (as USUAL)
    25.000.000 iterations
    total application time: 8.63
    total real time: 9.03

    Where RuntimeTest of Yours is:

    Size of USUAL 28
    Size of xUSUAL 16
    Size of FLOAT 16
    Size of xFLOAT 12

    Memory for 1M element ARRAY 40 000 056 bytes
    Memory for 1M element xARRAY 16 060 652 bytes

    Testing XSharp USUAL & Float
    25.000.000 iterations
    Time elapsed: 00:00:07.3690000

    Testing Vulcan USUAL & Float
    25.000.000 iterations
    Time elapsed: 00:00:40.1090000