Chrome: DOM engine performance

Each web browser has two separate engines, one for the implementation of JavaScript and one for the DOM. When you call a JavaScript function, you're using the JavaScript engine, while when you call a DOM method, you're using the DOM engine. Traditionally the DOM engine has always been slower than its JavaScript counterpart, so browser vendors put much effort in optimizing the performance of this engine. A particular case is Chrome, which clearly shows how the optimization of the DOM engine can be actually pushed to the limits. This video explains the details of such optimization.

JavaScript performance of the V8 engine

V8 is the JavaScript engine used by Google Chrome. This talk from Google explains why JavaScript performance is so important and how it's been implemented in V8 and Chrome. The point is, however, that testing your scripts only with an engine like V8 is somewhat confusing, in the sense that you may notice a big slowdown in other browsers, such as Internet Explorer. So it's important to test your script performance first in slow browsers and then in V8 and Chrome. By doing so, you make sure that the code you wrote works well even in slow browsers. wink

JavaScript: V8 engine preprocessing

One of the main files used by the V8 engine during JavaScript preprocessing is located in the folder /v8/src of the Chromium source code and is called preparser.cc. This file has the task of recognizing and separate input tokens that will be later feed to the JavaScript processor. For example, a main goal of a JavaScript preprocessor is the ability of recognizing JavaScript statements (i.e. all the core statements defined by the current ECMAScript specifications). V8 does the following:

PreParser::SourceElements PreParser::ParseSourceElements(int end_token,
                                                         bool* ok) {
  // SourceElements ::
  //   (Statement)* <end_token>

  while (peek() != end_token) {
    ParseStatement(CHECK_OK);
  }
  return kUnknownSourceElements;
}

The ParseStatement() method, which does all the work, is defined later in the source:

PreParser::Statement PreParser::ParseStatement(bool* ok) {
  // Statement ::
  //   Block
  //   VariableStatement
  //   EmptyStatement
  //   ExpressionStatement
  //   IfStatement
  //   IterationStatement
  //   ContinueStatement
  //   BreakStatement
  //   ReturnStatement
  //   WithStatement
  //   LabelledStatement
  //   SwitchStatement
  //   ThrowStatement
  //   TryStatement
  //   DebuggerStatement

  // Note: Since labels can only be used by 'break' and 'continue'
  // statements, which themselves are only valid within blocks,
  // iterations or 'switch' statements (i.e., BreakableStatements),
  // labels can be simply ignored in all other cases; except for
  // trivial labeled break statements 'label: break label' which is
  // parsed into an empty statement.

  // Keep the source position of the statement
  switch (peek()) {
    case i::Token::LBRACE:
      return ParseBlock(ok);

    case i::Token::CONST:
    case i::Token::VAR:
      return ParseVariableStatement(ok);

    case i::Token::SEMICOLON:
      Next();
      return kUnknownStatement;

    case i::Token::IF:
      return  ParseIfStatement(ok);

    case i::Token::DO:
      return ParseDoWhileStatement(ok);

    case i::Token::WHILE:
      return ParseWhileStatement(ok);

    case i::Token::FOR:
      return ParseForStatement(ok);

    case i::Token::CONTINUE:
      return ParseContinueStatement(ok);

    case i::Token::BREAK:
      return ParseBreakStatement(ok);

    case i::Token::RETURN:
      return ParseReturnStatement(ok);

    case i::Token::WITH:
      return ParseWithStatement(ok);

    case i::Token::SWITCH:
      return ParseSwitchStatement(ok);

    case i::Token::THROW:
      return ParseThrowStatement(ok);

    case i::Token::TRY:
      return ParseTryStatement(ok);

    case i::Token::FUNCTION:
      return ParseFunctionDeclaration(ok);

    case i::Token::NATIVE:
      return ParseNativeDeclaration(ok);

    case i::Token::DEBUGGER:
      return ParseDebuggerStatement(ok);

    default:
      return ParseExpressionOrLabelledStatement(ok);
  }
}

As you can see, the above method performs a sequential check using a switch statement. On each step, which corresponds to a different input token, an appropriate method is called on each token, depending on the type of JavaScript component encountered. Note that this is a one-way check, because each case statement terminates with a return statement so that the break statement is not required.

Chrome: calm like a bomb

ChromeGoogle Chrome has raised exponentially in the browser market during the last two years. Now its share is approximately 15% or 25%, depending on the site. Surely Chrome is gaining its momentum, but I think that it didn't express its full potential yet. What we got so far? A full standard compliant browser, really fast in loading pages and executing complex JavaScript scripts (a must for most RIAs). Is it enough? Certainly not! There are other possible scenarios for Chrome, which include:

  • full integration with Google web services, such as Gmail and Google Maps; in other words, more like a suite than a simple browser (see Seamonkey)
  • new features added with extensions
  • full support to HTML5
  • improved performance

Is it enough? I guess so! But the most exciting things are yet to come (Geolocations API, Microformats, just to name few). Time will tell if Chrome will get on the top of the browser market or be just another fad.

Chrome, Safari and document.lastModified

Perhaps I'm reinventing the wheel, but still. I was wondering why Safari and Chrome fail to correctly insert a string with the last modification date at the bottom of my pages. As usual, I've set up a test to check out what's wrong.

Surprisingly, both Chrome and Safari support the lastModified property of the document object only when the page is on a web server. However, they still fail to insert a date created as a timestamp from lastModified. I think this is somewhat related with a couple of problems that affected older versions of Webkit, mainly due to some obscure details of the implementation of the Date object (Safari 3 returned NaN, for example).

So they don't display the timestamp and I think that I'll have to make some further tests to fix this problem. Stay tuned!