Hi guys,

we have fully investigated the example provided by Caio, and want to share
the excellent news with you. As we said before, the "with + function"
construct in JavaScript creates static bindings, which means there is no
dynamic lookup after the function is compiled! This is proved by callgrind
as well.

Peter created a simple benchmark which uses v8 from qtscript-staging. The
benchmark is based on the pattern provided by Caio before, and tests
several different alternatives:

* Normal JS variables / accessor (callback) functions
* With statement / push context API
* Crankshaft is enabled or not

The benchmark itself is attached to this mail.

With crankshaft:
  JS accessors:  19.8 s
  JS variables:  19.8 s
  API accessors: 19.8 s
  API variables: 18.4 s

Without crankshaft:
  JS accessors:  19.8 s
  JS variables:  19.8 s
  API accessors: 19.7 s
  API variables: 18.4 s

As you can see there is no difference (the numbers varies a little
depending the execution order of the tests, but still, the difference is
negligible).

As for cunclusion I would suggest the with statement, since if you happens
to change the JS engine again, it will still work.

I think this is really a good news, since we don't need to worry about the
lookup speed, since there are no lookups here.

Regards,
Zoltan

> Hello Peter,
>
>> 4. We can reach better performance with "with" if we use function inside
>> "with":
>> with(o) (function() { for(var i = 0; i < 1000000; ++i) Math.sin(i); })()
>> time: 221msec
>>
>> The fourth example just a suggestion to using "with" statement and reach
>> better performace without modify the JS engine.
>> Would it be possible to use the latest form for your purposes?
>
> We might be confusing things here. I'll give a sketch of how the
> bindings are created and evaluated, imagine this snippet of QML
>
> MyItem {
>    prop: valueA + valueB + Math.sin(0)
> }
>
> what the engine currently does is (you can checkout
> src/declarative/qdeclarativeexpression.cpp for more detais)
>
> 1) push a "clean" context (which is a function context in JS terms),
> pushContext() from qtscript API
> 2) create an object that represents the context of the expression,
> this object is from a special scriptclass that will perform lookup in
> the properties of the object and other things QML provide.
> 3) create an object that mimics the global object (has Math, etc).
>
> Then both objects (2) and (3) are pushed as scopes (which are with
> contexts in JS terms), the original expression is rewritten as a
> function:
>
> (function() { valueA + valueB + Math.sin(0) })
>
> and evaluated, the value is stored and the clean context is popped.
>
> An _approximation_ in JS of what we do would be:
>
> (function() { // this is the "clean" context
>   var context = Engine.createDeclarativeContextForObject(obj);
>   var newGlobal = Engine.getStaticGlobalObject();
>
>   with (context) {
>     with (newGlobal) {
>       return (function() { valueA + valueB + Math.sin(0) });
>     }
>   }
> })(); // we evaluate, store the value and close the "clean" context
>
> The returned value is a function, when executed will give us the value
> of the expression. QML re-executes it when it knows some dependency
> changed.
>
> So this is the way that works right now. We get the repeated execution
> of the "whole package". Are there ways to improve things in this
> scenario?
>
> Our static global object is "static" (has a fixed set of properties),
> could that information do improve lookup speed when inside with()
> contexts?
>
>
> Last month I posted to the list that there's an edge case in Web that
> a similar dynamic behavior is necessary, and they implement it in a
> very similar fashion but without C++ APIs. Maybe this can give some
> insight, or help understanding our case better.
>
> http://thread.gmane.org/gmane.comp.lang.javascript.v8.general/3738/focus=3743
>
> the actual code / comments are in this file:
>
> http://trac.webkit.org/browser/trunk/Source/WebCore/bindings/v8/V8LazyEventListener.cpp
>
>
> Cheers,
>
> --
> Caio Marcelo de Oliveira Filho
> OpenBossa - INdT
> _______________________________________________
> Qt-script mailing list
> [email protected]
> http://lists.qt.nokia.com/mailman/listinfo/qt-script
>
#include <v8.h>
#include <sys/time.h>

using namespace v8;

static inline Local<v8::Value> CompileRun(const char* source) {
  return Script::Compile(v8::String::New(source))->Run();
}

#if 0
const char* ToCString(const v8::String::Utf8Value& value)
{
  return *value ? *value : "<string conversion failed>";
}

Handle<Value> Print(const v8::Arguments& args)
{
  bool first = true;
  for (int i = 0; i < args.Length(); i++) {
    v8::HandleScope handle_scope;
    if (first) {
      first = false;
    } else {
      printf(" ");
    }
    v8::String::Utf8Value str(args[i]);
    const char* cstr = ToCString(str);
    printf("%s", cstr);
  }
  printf("\n");
  fflush(stdout);
  return v8::Undefined();
}
#endif

Handle<Value> getter(Local<String> property,
                     const AccessorInfo& info)
{
  static int x = 0;
  return Integer::New(x++);
}

void setter(Local<String> property, Local<Value> value,
            const AccessorInfo& info)
{}

void measure(Local<Function> fun, const char* info)
{
  struct timeval start, end;

  gettimeofday(&start, NULL);
  for (int i = 0; i < 100000; ++i)
    fun->Call(fun, 0, NULL);
  gettimeofday(&end, NULL);

  double tS = start.tv_sec*1000000 + start.tv_usec;
  double tE = end.tv_sec*1000000 + end.tv_usec;
  printf("%s  %.0f\n", info, tE - tS);
}

void benchAPI(bool withAccessor = false)
{
  HandleScope handleScope;

  // Create global context
  Handle<ObjectTemplate> global = ObjectTemplate::New();
  Persistent<Context> context = Context::New(NULL, global);
  context->Enter();

  // Create a custom object
  Local<ObjectTemplate> instanceTemplate = ObjectTemplate::New();
  Local<Object> instance = instanceTemplate->NewInstance();

  if (withAccessor) {
    instance->SetAccessor(String::New("valueA"), getter, setter);
    instance->SetAccessor(String::New("valueB"), getter, setter);
  } else {
    instance->Set(String::New("valueA"), Number::New(3));
    instance->Set(String::New("valueB"), Number::New(4));
  }

  // Push a clean context
  Local<Context> scriptContext = Context::NewFunctionContext();
  scriptContext->Enter();

  // Push the custom object's scope
  Local<Context> instanceContext = scriptContext->NewScopeContext(instance);
  instanceContext->Enter();
  // Push the global object's scope
  Local<Context> globalContext = scriptContext->NewScopeContext(context->Global());
  globalContext->Enter();

  // Evaluate an expression
  Local<Script> code = Script::CompileEval(String::New("(function() { valueA + valueB + Math.sin(0) });"));
  Local<Value> result = code->Run();

  globalContext->Exit();
  instanceContext->Exit();

  // Close the clean context
  scriptContext->Exit();

  Local<Function> fun = Local<Function>::Cast(result);
  measure(fun, withAccessor ? "API accessor:" : "API: ");

  // Close the global context
  context->Exit();
  context.Dispose();
}

void benchJS(bool withAccessor = false)
{
  HandleScope handleScope;

  // Create global context
  Handle<ObjectTemplate> global = ObjectTemplate::New();
  Persistent<Context> context = Context::New(NULL, global);
  Context::Scope contextScope(context);

  Local<ObjectTemplate> instanceTemplate = ObjectTemplate::New();
  Local<Object> instance = instanceTemplate->NewInstance();
  if (withAccessor) {
    instance->SetAccessor(String::New("valueA"), getter, setter);
    instance->SetAccessor(String::New("valueB"), getter, setter);
  } else {
    instance->Set(String::New("valueA"), Number::New(3));
    instance->Set(String::New("valueB"), Number::New(4));
  }

  Local<Object> globalObject = context->Global();

  globalObject->Set(String::New("context"), instance);
  globalObject->Set(String::New("newGlobal"), globalObject);

  Local<Value> result = CompileRun(
      "(function() {"
      "   with(context)"
      "     with(newGlobal)"
      "       return (function() { valueA + valueB + Math.sin(0) });"
      "})();");

  Local<Function> fun = Local<Function>::Cast(result);
  measure(fun, withAccessor ? "JS accessor: " : "JS:  ");

  context.Dispose();
}

int main(int argc, char* argv[])
{
  benchJS(true);
  benchJS();
  benchAPI(true);
  benchAPI();

  return 0;
}

_______________________________________________
Qt-script mailing list
[email protected]
http://lists.qt.nokia.com/mailman/listinfo/qt-script

Reply via email to