5
\$\begingroup\$

This is my C# class, which takes 2.6s:

class Snake
{
    readonly Symbol _mySymbol;
    readonly int _multiplier;
    readonly int _hit;
    readonly int _payout;
    readonly Snake _bestSnake;

    public Symbol MySymbol { get { return _mySymbol; } }
    public int Multiplier { get { return _multiplier; } }
    public int Hit { get { return _hit; } }
    public int Payout { get { return _payout; } }

    public Snake BestSnake { get { return _bestSnake; } }

    public Snake(Apple myApple)
    {
        this._mySymbol = myApple.MySymbol;
        this._multiplier = myApple.Multiplier;
        this._hit = 1;
        this._payout = _mySymbol.Payout_[0] * (_multiplier == 0 ? 1 : _multiplier);
        this._bestSnake = this;
    }

    Snake(Symbol mySymbol, int multiplier, int hit)
    {
        this._mySymbol = mySymbol;
        this._multiplier = multiplier;
        this._hit = hit;
        this._payout = _mySymbol.Payout_[hit - 1] * (multiplier == 0 ? 1 : multiplier);
        this._bestSnake = this;
    }

    Snake(Symbol mySymbol, int multiplier, int hit, Snake bestRecentSnake)
        : this(mySymbol, multiplier, hit)
    {
        if (_payout < bestRecentSnake._payout)
        {
            this._bestSnake = bestRecentSnake;
        }
        else if (_payout == bestRecentSnake._payout)
        {
            if (bestRecentSnake._mySymbol.Type == 1 && mySymbol.Type != 1)
            {
                this._bestSnake = bestRecentSnake;
            }
        }
    }

    public Snake Eat(Apple nextApple)
    {
        var resultSymbol = Symbol.Add(_mySymbol, nextApple.MySymbol);
        if (resultSymbol == null)
            return null;

        var newSnake = new Snake(resultSymbol, Math.Max(_multiplier, nextApple.Multiplier), Hit + 1, _bestSnake);

        return newSnake;
    }

This is my F# class, which takes 5s:

type Snake (mySymbol: Symbol, multiplier, hit, best: Option<Snake>) =

let payout (mySymbol: Symbol) hit multiplier = 
    match multiplier with
    | 0 -> mySymbol.Payout_.[hit-1]
    | a -> mySymbol.Payout_.[hit-1] * a

new (myApple: Apple) = Snake (myApple.MySymbol, myApple.Multiplier, 1, None)

member this.BestSnake =
    match best with
    | Some a when this.Payout < a.Payout -> a
    | Some a when this.Payout = a.Payout && a.MySymbol.Type = 1 && mySymbol.Type <> 1 -> a
    | _ -> this

member this.MySymbol = mySymbol
member this.Payout = payout mySymbol hit multiplier
member this.Multiplier = multiplier
member this.Hit = hit

member this.Eat (myApple: Apple) =
    let tmp = Symbol.Add mySymbol myApple.MySymbol
    match tmp with
    | None -> Operators.Unchecked.defaultof<Snake>
    | Some a -> Snake (a, (max multiplier myApple.Multiplier), hit+1, Some this.BestSnake)
\$\endgroup\$
6
  • 2
    \$\begingroup\$ Could you also provide the code that you used to measure the performance? What exactly did you measure? \$\endgroup\$
    – svick
    Commented Sep 12, 2012 at 16:00
  • \$\begingroup\$ This doesn't address your question, but the whole point of using underscores before member variable names is so that you don't have to use this.. The underscore is a convention so that you always know what is a member variable instead of a method variable. It also allows you to never have to worry about a method variable shadowing a member variable. \$\endgroup\$ Commented Sep 12, 2012 at 16:00
  • \$\begingroup\$ On underscores: I strongly prefer this. over an underscore; so does Stylecop. I would also use auto-properties such as public int Multiplier {get; private set;} or have readonly fields (those can still be set inside of the constructor). \$\endgroup\$
    – Leonid
    Commented Sep 12, 2012 at 16:43
  • \$\begingroup\$ @svick sry it is too complicated if i attach the complete code. I measured the whole project which reference this class (before switch to F# is 2.6s vs after is 5s) \$\endgroup\$
    – colinfang
    Commented Sep 12, 2012 at 16:46
  • 1
    \$\begingroup\$ One obvious difference I can see is that the F# version of the BestSnake property does work every time you call it, while the C# version simply returns a pre-calculated value every time you call it. Whether this matters or not to your performance measurement depends entirely on code that's not included here. \$\endgroup\$ Commented Sep 12, 2012 at 17:54

1 Answer 1

5
\$\begingroup\$

There are a few small differences in your translation. I would start with as literal a translation possible, then optimize/make idiomatic from there. If done incrementally it should be easy to spot performance problems as they occur. Here's a starting point:

(* stub *)
[<AllowNullLiteral>]
type Symbol =
  member x.Payout = [|0|]
  member x.Type = 0
  static member Add(sym1, sym2) : Symbol = null

(* stub *)
type Apple =
  abstract MySymbol : Symbol
  abstract Multiplier : int

type private BestSnake =
  | This
  | RecentSnake of Snake

and [<AllowNullLiteral>] Snake private (mySymbol: Symbol, multiplier, hit, bestSnake) as this =
  let payout = mySymbol.Payout.[hit - 1] * (match multiplier with 0 -> 1 | m -> m)
  let bestSnake =
    match bestSnake with
    | This -> this
    | RecentSnake bestRecentSnake ->
      if payout < bestRecentSnake.Payout || 
        (payout = bestRecentSnake.Payout && bestRecentSnake.MySymbol.Type = 1 && mySymbol.Type <> 1) 
      then bestRecentSnake
      else null

  member val MySymbol : Symbol = mySymbol
  member val Multiplier = multiplier
  member val Hit = hit
  member val BestSnake = bestSnake
  member val Payout = payout

  new (mySymbol, multiplier, hit, bestRecentSnake) = Snake(mySymbol, multiplier, hit, RecentSnake bestRecentSnake)
  new (mySymbol, multiplier, hit) = Snake(mySymbol, multiplier, hit, This)
  new (myApple: Apple) = Snake(myApple.MySymbol, myApple.Multiplier, 1, This)

  member this.Eat(nextApple: Apple) =
    match Symbol.Add(this.MySymbol, nextApple.MySymbol) with
    | null -> null
    | resultSymbol -> Snake(resultSymbol, max this.Multiplier nextApple.Multiplier, this.Hit + 1, this.BestSnake)

The performance of this should be on par with C#.

\$\endgroup\$
6
  • \$\begingroup\$ I notice u used member val, is there any difference between to use/not to use val if the property is readonly of a default parameter? e.g. member val Hit = hit vs member Hit = hit \$\endgroup\$
    – colinfang
    Commented Sep 12, 2012 at 20:33
  • \$\begingroup\$ I used it because it guarantees (and therefore makes explicit) that the right-hand side is only evaluated once. If the right-hand side is a constant (as in this case) there is no difference. \$\endgroup\$
    – Daniel
    Commented Sep 12, 2012 at 20:47
  • \$\begingroup\$ new (myApple: Apple) as this = Snake(myApple.MySymbol, myApple.Multiplier, 1, this) at runtime raises {"The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized."} \$\endgroup\$
    – colinfang
    Commented Sep 13, 2012 at 11:13
  • \$\begingroup\$ Oops—I didn't test it. It doesn't like passing this to another constructor. An alternate solution is to use a discriminated union to represent the different values that can be assigned to bestSnake. I've updated the code to demonstrate. I tested this time and it works. \$\endgroup\$
    – Daniel
    Commented Sep 13, 2012 at 14:28
  • \$\begingroup\$ thx, i have updated my question with latest improved F# code, still it is a bit slower than C#, I also attached some results from profiling tool. Even if I inline Symbol.Add, it would take 3 s which is longer than 2.6s \$\endgroup\$
    – colinfang
    Commented Sep 13, 2012 at 21:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.