4

I've been working on polishing up my JSON code for my ECMAScript runtime and I decided to run an experiment. The following str function has 4 logical steps which I've broken up into functions and marked them inline.

and private str (state:StringifyState) (key:string) (holder:IObject) : IDynamic =
    let inline fourth (value:IDynamic) =
        match value.TypeCode with
        | LanguageTypeCode.Null -> 
            state.environment.CreateString "null" :> IDynamic
        | LanguageTypeCode.Boolean -> 
            let v = value :?> IBoolean
            state.environment.CreateString (if v.BaseValue then "true" else "false") :> IDynamic
        | LanguageTypeCode.String -> 
            let v = value :?> IString
            state.environment.CreateString (quote v.BaseValue) :> IDynamic
        | LanguageTypeCode.Number -> 
            let v = value :?> INumber
            if not (Double.IsInfinity(v.BaseValue)) 
            then v.ConvertToString() :> IDynamic 
            else state.environment.CreateString "null" :> IDynamic
        | LanguageTypeCode.Object -> 
            let v = value :?> IObject
            let v = if v.Class = "Array" then ja state v else jo state v 
            state.environment.CreateString v :> IDynamic
        | _ -> 
            state.environment.Undefined :> IDynamic
    let inline third (value:IDynamic) =
        match value.TypeCode with
        | LanguageTypeCode.Object ->
            let v = value :?> IObject
            match v.Class with
            | "Number" -> 
                fourth (v.ConvertToNumber())
            | "String" ->
                fourth (v.ConvertToString())
            | "Boolean" ->
                fourth (v.ConvertToBoolean())
            | _ -> 
                fourth value
        | _ -> 
            fourth value
    let inline second (value:IDynamic) =
        match state.replacerFunction with
        | :? ICallable as f ->
            let args = state.environment.CreateArgs ([| state.environment.CreateString key :> IDynamic; value |])
            let value = f.Call (state.environment, holder :> IDynamic, args)
            third value
        | _ -> 
            third value 
    let inline first (value:IDynamic) =
        match value with
        | :? IObject as v ->
            let toJSON = v.Get "toJSON"
            match toJSON with
            | :? ICallable as f ->
                let args = state.environment.CreateArgs ([| state.environment.CreateString key :> IDynamic |])
                let value = f.Call (state.environment, value, args) 
                second value
            | _ -> 
                second value
        | _ -> 
            second value
    first (holder.Get key)

I compiled with full optimizations and opened up the resulting assembly with Reflector to see the results.

[CompilationArgumentCounts(new int[] { 1, 1, 1 })]
internal static IDynamic str(StringifyState state, string key, IObject holder)
{
    IObject obj3;
    ICallable callable;
    ICallable callable2;
    IArgs args;
    IDynamic dynamic3;
    IDynamic dynamic4;
    ICallable callable3;
    IDynamic dynamic5;
    IBoolean flag;
    IString str;
    INumber number;
    IObject obj4;
    string str2;
    INumber number2;
    IObject obj5;
    string str3;
    IString str4;
    IBoolean flag2;
    IDynamic thisBinding = holder.Get(key);
    IObject obj2 = thisBinding as IObject;
    if (obj2 == null)
    {
        callable = state.replacerFunction@ as ICallable;
        if (callable == null)
        {
            switch (thisBinding.TypeCode)
            {
                case LanguageTypeCode.Object:
                    obj3 = (IObject) thisBinding;
                    str2 = obj3.Class;
                    if (!string.Equals(str2, "Number"))
                    {
                        if (string.Equals(str2, "String"))
                        {
                            dynamic3 = obj3.ConvertToString();
                            switch (dynamic3.TypeCode)
                            {
                                case LanguageTypeCode.Null:
                                    return (IDynamic) state.environment@.CreateString("null");

                                case LanguageTypeCode.Boolean:
                                    flag = (IBoolean) dynamic3;
                                    return (IDynamic) state.environment@.CreateString(!flag.BaseValue ? "false" : "true");

                                case LanguageTypeCode.String:
                                    str4 = (IString) dynamic3;
                                    return (IDynamic) state.environment@.CreateString(quote(str4.BaseValue));

                                case LanguageTypeCode.Number:
                                    number = (INumber) dynamic3;
                                    if (double.IsInfinity(number.BaseValue))
                                    {
                                        return (IDynamic) state.environment@.CreateString("null");
                                    }
                                    return (IDynamic) number.ConvertToString();

    // ... I removed a large amount of code. 

    return (IDynamic) state.environment@.Undefined;
}

Clearly the inline modifier is quite literal. The code is quite huge and with some preliminary tests is very efficient. One might consider throwing inline on all of their functions if they didn't care about the size of the resulting assemblies. What are some guidelines I can follow to know when the use of inline is appropriate? If possible I would like to avoid having to measure performance every single time to determine this.

ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
  • Have you read the Search "Draft F# Component Design Guidelines (August 2010)"? http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/fsharp-component-design-guidelines.pdf – Mitch Wheat Dec 28 '10 at 03:18
  • possible duplicate?: Use of `inline` in F#: http://stackoverflow.com/questions/3754862/use-of-inline-in-f ?? – Mitch Wheat Dec 28 '10 at 03:19
  • @Mitch - Its possible my question may not be as sound as I originally thought but I feel it has a different focus compared to the one you linked. – ChaosPandion Dec 28 '10 at 03:41

2 Answers2

4

If you are using inline solely for performance considerations, then I think that all of the typical performance-related advice applies. Most importantly, set a performance target and profile your application for hotspots. Then use inline if you have reason to believe that it will improve performance, and test to verify that it does. Keep in mind that the IL that the F# compiler generates is JIT compiled anyway, so small functions (in terms of IL size) may be inlined in the compilation to machine code even if you don't use inline in your F# code.

I typically only use inline when I want to use statically resolved type variables (e.g. because of member constraints).

kvb
  • 54,864
  • 2
  • 91
  • 133
  • This is literally what I've been thinking but having someone else say it pretty much solidifies my position. I am gonna hold off on choosing an answer for a while to see if we get any other gurus with a bit of advice. – ChaosPandion Dec 28 '10 at 03:43
2

I agree with kvb's answer, but here are two specific reasons not to

consider throwing inline on all of their functions if they didn't care about the size of the resulting assemblies.

  1. The obvious case is that inlining anonymous functions won't work.

  2. More inlining (especially of big functions) -> less (effectively) code fits into cache -> the program works slower.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487