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\$
  • 2
    \$\begingroup\$ Could you also provide the code that you used to measure the performance? What exactly did you measure? \$\endgroup\$ – svick Sep 12 '12 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\$ – unholysampler Sep 12 '12 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 Sep 12 '12 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 Sep 12 '12 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\$ – Joel Mueller Sep 12 '12 at 17:54
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\$
  • \$\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 Sep 12 '12 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 Sep 12 '12 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 Sep 13 '12 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 Sep 13 '12 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 Sep 13 '12 at 21:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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