Fork me on GitHub
#clojurescript
<
2022-01-08
>
ribelo15:01:44

What is going on underneath here that makes such a difference?

(defn fn1 [f xs]
  (loop [[x & ?more] xs acc 0]
    (if x
      (recur ?more (f acc x))
      acc)))

(defn fn2 [xs]
  (loop [[x & ?more] xs acc 0]
    (if x
      (recur ?more (+ acc x))
      acc)))

(let [xs (vec (range 1e5))]
  (enc/qb 1e2
    (fn1 + xs)
    (fn2 xs)))
;; => [879 541] ; milliseconds

ribelo15:01:41

functions are basically the same, except that one takes + as arg and the other uses it directly

ribelo15:01:07

thanks to http://app.klipse.tech/, it can be seen that f.call is used in the first case, but how does reduce not have a problem with it?

p-himik15:01:39

+ is both a macro and a function. That's probably the reason.

p-himik15:01:11

In fn1, the function is used. In fn2, the macro, which in JS becomes just plain acc + x.

ribelo15:01:22

(let [xs (vec (range 1e5))]
  (enc/qb 1e2
    (fn1 (fn [x y] (+ x y)) xs)
    (fn2 xs)))
;; => [637 553]

ribelo15:01:37

much better

ribelo15:01:00

:thinking_face:

p-himik15:01:46

In this case, it may be the fact that (fn [x y] ...) is a function with a fixed arity of 2 whereas + is a function with arbitrary arity which in JS becomes (at least when I check) a switch on the number of its arguments.

p-himik15:01:19

Also, if you really care about performance you should try switching from destructuring to indexing, at least when you know for sure that your argument is a vector and not a list/seq/something else.

p-himik15:01:06

And I'm not sure what exactly you're trying to do here, but you can definitely just (apply + ...) or maybe use reduce.

ribelo15:01:38

this is just an idiotic example which was meant to be stupid but understandable

p-himik15:01:48

You also asked: > how does `reduce` not have a problem with it? But you didn't use reduce above. What is its timing?

p-himik15:01:27

Ah, and yeah - it still has the problem with + being a function with arbitrary artity. But it's probably faster just because it doesn't use destructuring.

ribelo15:01:43

I am interested in whether it is possible to somehow get rid of the overhead of passing f as an argument in CLJS. In CLJ this problem does not exist.

ribelo15:01:27

CLJ

(let [xs (vec (range 1e5))]
  (enc/qb 1e2
    (fn1 + xs)
    (fn2 xs)))
;; => [144.97 144.17] ;ms

ribelo15:01:15

I discovered this "problem" by accident, because my fast functions after moving to CLJS were no longer fast 😛

ribelo15:01:32

all tight loops suffer greatly every time a function is passed as an argument

p-himik15:01:15

Oh maybe it's your REPL, whatever you're using for it. For your initial example, in a debug build of an app running in Chrome, I got [945 926] - barely any difference.

ribelo15:01:28

I use shadow-cljs browser repl in this example

p-himik15:01:44

Chrome or something else?

ribelo15:01:15

I checking node-repl

p-himik15:01:57

Interesting. As far as I'm concern, it should work the same, unless we have versions of Chrome that are drastically different. There's a chance something might've happened in CLJS itself, but I doubt that. But also, performance measurements can be finicky. Different browsers, different flags, different platforms, slightly different JS code,...

p-himik15:01:21

Some additional measurements from my end:

[:destructure+fn 922]
[:destructure+macro 864]
[:index-loop+fn 699]
[:reduce 195]
[:apply 202]

ribelo15:01:12

strange, on node-repl the result is roughly identical

p-himik15:01:52

So maybe something strange is going on with shadow-cljs' browser REPL. I haven't tried that - I just chucked that code into an existing app and ran it from there.

ribelo15:01:53

I will check in firefox, and if there are identical anomalies, I will ask on shadow-cljs channel

ribelo15:01:20

thx mate 😉

p-himik15:01:36

Also, if your loops are really tight and you're operating on some data that previously was JS arrays, it might make sense to keep using arrays and maybe even JS's reduce. There won't be anything faster than that.

p-himik15:01:41

No problem.

ribelo15:01:55

in between is iter

p-himik15:01:37

But it also isn't documented - not sure what's up with that.

ribelo16:01:55

quite a few libraries use iter, because although it is not documented and is supposedly hidden, it is much faster

ribelo16:01:13

e.g. meander

p-himik16:01:54

That link isn't really applicable since it's about Clojure, which doesn't have iter.

ribelo16:01:44

(.iterator xs)

p-himik16:01:55

Well, not what I meant. :)

ribelo16:01:49

I understand what you mean, but every clojure collection is iterable, so you can use this without any problem

ribelo16:01:55

generally there is no need to use it in 99.9% of cases, but in this 0.1% it may be useful

p-himik16:01:46

Depends on what you mean by "without any problem". My whole point is that cljs.core/iter might not be public, even though it doesn't use defn- - simply because it has no docstring for some reason.

ribelo16:01:20

non-public functions can also be used, but I don't think that's what this is all about

lilactown18:01:51

the best thing to do in these benchmarks is to profile it

lilactown18:01:06

not sure yet how to attach chrome dev tools to a node REPL but that's where I'd start I see you're using a browser REPL so it should be easy to use the Profile tab in Chrome dev tools

lilactown18:01:41

you might also find that things that are slow from a REPL are fast when used in your application, because most CLJS apps are released using advanced optimizations which can inline things, remove IIFEs and other optimizations

👍 1
Carlo15:01:37

hey, I'd like to understand better why js->clj sometimes doesn't work, and what I can do about it. Consider this example, when there is some selected text

(js->clj (js/document.getSelection))
;; => #object[Selection voluptate]

(type (js->clj (js/document.getSelection)))
;; => #object[Selection]

(type (js/document.getSelection))
;; => #object[Selection]
I want the actual clj map corresponding to that object instead. I think one reason here could be that the selection is not just a js Object, but a Selection, and if that's the case I'd like to know why that's a problem

p-himik15:01:21

Because js->clj is intended for plain JS objects, not for instances of particular classes. I don't know for sure but my guess is that it's done to prevent people from overusing it, like you're trying to do in your scenario - if you want some particular fields, just access those fields via interop.

Carlo15:01:15

Here's why I care: the fact that I can't visualize the converted value in my editor breaks my repl workflow; maybe there's a better way of doing that. Is this restriction added for performance reasons?

p-himik15:01:53

So all you want is to output the value in a readable way in a plain-text REPL? If so, you definitely should look into tap> and how to use it along with Portal or Reveal. Alternatively, as an easy way of doing similar thing once, you can simply (js/console.log ...) that value and check it out in full in your browser's JS console. It's not for performance reasons. js->clj is for plain data, that's it. It was never intended to work with instances of classes. (at least, that's my personal take on it) If you really do prefer to use plain text REPL over much more rich tools like Portal and Reveal, then you can look into cljs-bean and use it instead of js->clj.

🙌 1
Carlo16:01:46

thanks @U2FRKM4TW your answers are always incredibly informative!

👍 1
mfikes16:01:35

FWIW, cljs-bean was written with plain data in mind as well, but it might work to expose regular objects in a suitable way. (There have certainly been a few bug reports related to that use when people hit corner cases that aren't handled cleanly.)

👍 2
borkdude17:01:08

$ nbb -e "(require '[cljs-bean.core :as b]) (def c (b/bean js/console)) ((:log c) \"hello\")"
hello

borkdude17:01:08

similarly:

$ nbb -e "(require '[applied-science.js-interop :as j]) (def c (j/lookup js/console)) ((:log c) \"hello\")"
hello

borkdude17:01:01

probably doesn't work for objects that are using "this" in their attached functions, or so... caveat emptor.

raspasov06:01:15

For inspecting JavaScript objects of various kinds that are not plain data js-keys has been useful.

👍 1