Welcome! Please see the About page for a little more info on how this works.

0 votes
in ClojureScript by
There are some optimizations that we can make for the {{simple-ident?}}, {{qualified-ident?}}, {{simple-symbol?}}, {{qualified-symbol?}}, {{simple-keyword?}}, and {{qualified-keyword?}} functions.

Two separate optimization can be done.

In places where {{boolean}} is used (the {{qualified-}} fns), we can instead use {{some?}} for a faster implementation:


(boolean (and (symbol? x) (namespace x) true))


can be converted to simply


(and (symbol? x) (some? (namespace x)))


Focusing on just V8 for now, for


(simple-benchmark [x 'a/b f #(qualified-symbol? x)] (f) 100000000)


and subtracting out the baseline cost for


(simple-benchmark [x 'a/b f (fn [] x)] (f) 100000000)


this yields a speedup of 1.74.

Since we know that we are dealing with keywords or symbols when applying {{namespace}}, we can furthermore use  {{(.-ns x)}}.

Alone this gives a speedup of 2.21 and when combined with the previous optimization as


(and (symbol? x) (some? (.-ns x)))


we see a combined speedup approaching infinity (the operation effectively becomes free, taking the same time as the baseline).

Note that we could perhaps use predicate-induced type inference along with a new macro for {{namespace}} that emits {{.-ns}} instead, but CLJS-2866 currently hinges on looking at the test in an {{if}}, while expressions like the above cause optimized {{js*}} output with short-circuiting {{&&}}.

These optimizations would be good because these functions are often used in spec-based generative code testing where performance can be an issue.

We have test coverage for these functions in {{cljs.predicates-test}}.

For reference here is the JavaScript currently emitted for {{qualified-symbol?}}


function cljs$core$qualified_symbol_QMARK_(x) {
  return cljs.core.boolean$(
    (function() {
      var and__8540__auto__ = x instanceof cljs.core.Symbol;
      if (and__8540__auto__) {
        var and__8540__auto____$1 = cljs.core.namespace(x);
        if (cljs.core.truth_(and__8540__auto____$1)) {
          return true;
        } else {
          return and__8540__auto____$1;
        }
      } else {
        return and__8540__auto__;
      }
    })()
  );
}


and here is what we get with the revisions


function cljs$user$qualified_symbol_QMARK_(x) {
  return x instanceof cljs.core.Symbol && !(x.ns == null);
}


(The compactness of this revised implementation means that it might be further inlinable via Closure for more speedups for whole-program optimization.)

9 Answers

0 votes
by

Comment made by: mfikes

Note, Chris has submitted the CA (Same as @hlprmnky https://twitter.com/hlprmnky/status/1043661374830911489)

0 votes
by

Comment made by: cjbidler

Patch with changes as outlined in ticket. All tests pass.

0 votes
by

Comment made by: mfikes

Hey Chris, Please attach a revised patch which doesn't update the {{def}} for * }. (You can name the patch CLJS-2920-2.patch and it should be a standalone patch, not dependent on the previous patch.)

0 votes
by

Comment made by: cjbidler

As directed; thanks for catching that. For future reference, is resetting that def a manual cleanup step after running lein uberjar? If there's an automated thing I can do to protect myself from this mistake in future patch submissions, I would love to know about it. :D

0 votes
by

Comment made by: mfikes

I'm not aware of an automated mechanism to clean up the * } revision. I usually just try to avoid staging that particular change in the commit.

0 votes
by

Comment made by: mfikes

CLJS-2920-2.patch LGTM and passes all Canary tests.

0 votes
by

Comment made by: mfikes

CLJS-2920-2.patch no longer applies

0 votes
by

Comment made by: mfikes

Actually patch applies fine if you include {{--3way}} option, so no need to rebaseline.

0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJS-2920 (reported by mfikes)
...