user1637281
user1637281

Reputation:

Is array indexing costly?

In the snippet below from backbone, this line

(ev = events[i]).callback.call(ev.ctx);

is not clear. I would like to assume that ev = events[i] is interpreted before ev.ctx as the variable seems to have no other purpose except to reduce array indexing.

Assuming this is correct, is this done because array indexing is costly? I know in embedded engineering array indexing is costly when you are working with limited resources. But I thought this was not such a concern in JavaScript.

Backbone Snippet

triggerEvents = function(events, args) {
    var ev, 
        i = -1, 
        l = events.length;
    switch (args.length) {
        case 0: 
            while (++i < l) {
                (ev = events[i]).callback.call(ev.ctx);
            }
            return;
        case 1: 
            while (++i < l) {
               (ev = events[i]).callback.call(ev.ctx, args[0]);
            }
            return;
        case 2: 
            while (++i < l) { 
                (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
            }
            return;
        case 3: 
            while (++i < l) { 
                (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
            }
            return;
        default: 
            while (++i < l) {
                (ev = events[i]).callback.apply(ev.ctx, args);
            }
    }
};

Upvotes: 3

Views: 154

Answers (1)

Jason Sperske
Jason Sperske

Reputation: 30456

OK using the treehugger.js demo to generate an AST of the backbone.js approach with this simplified bit of code:

var ev, i = -1, l = 3;
while (++i < l) { 
  (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
}

Produces this AST:

[
  VarDecls(
      [
        VarDecl("ev"),
        VarDeclInit(
            "i",
            PrefixOp(
                "-",
                Num("1")
            )
        ),
        VarDeclInit(
            "l",
            Num("3")
        )
      ]
  ),
  While(
      Op(
          "<",
          PrefixOp(
              "++",
              Var("i")
          ),
          Var("l")
      ),
      Block(
          [
            Call(
                PropAccess(
                    PropAccess(
                        Assign(
                            Var("ev"),
                            Index(
                                Var("events"),
                                Var("i")
                            )
                        ),
                        "callback"
                    ),
                    "call"
                ),
                [
                  PropAccess(
                      Var("ev"),
                      "ctx"
                  ),
                  Index(
                      Var("args"),
                      Num("0")
                  ),
                  Index(
                      Var("args"),
                      Num("1")
                  ),
                  Index(
                      Var("args"),
                      Num("2")
                  )
                ]
            )
          ]
      )
  )
]

While the implied "cleaner" approach:

var ev, i = -1, l = 3;
while (++i < l) {
  ev = events[i];
  ev.callback.call(ev.ctx, args[0], args[1], args[2]);
}

Produces this AST:

[
  VarDecls(
      [
        VarDecl("ev"),
        VarDeclInit(
            "i",
            PrefixOp(
                "-",
                Num("1")
            )
        ),
        VarDeclInit(
            "l",
            Num("3")
        )
      ]
  ),
  While(
      Op(
          "<",
          PrefixOp(
              "++",
              Var("i")
          ),
          Var("l")
      ),
      Block(
          [
            Assign(
                Var("ev"),
                Index(
                    Var("events"),
                    Var("i")
                )
            ),
            Call(
                PropAccess(
                    PropAccess(
                        Var("ev"),
                        "callback"
                    ),
                    "call"
                ),
                [
                  PropAccess(
                      Var("ev"),
                      "ctx"
                  ),
                  Index(
                      Var("args"),
                      Num("0")
                  ),
                  Index(
                      Var("args"),
                      Num("1")
                  ),
                  Index(
                      Var("args"),
                      Num("2")
                  )
                ]
            )
          ]
      )
  )
]

The only difference between these two is the Assign() takes place inside the PropAccess() while in the "cleaner" approach has it outside. They have the same number of steps. You could avoid the Assign all together and swap it with a second Index. I'm afraid I don't know enough about JS internals to know if this is faster. I would guess it is platform dependent.

Upvotes: 1

Related Questions