This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-06-26
Channels
- # aleph (5)
- # announcements (16)
- # babashka (36)
- # beginners (161)
- # calva (24)
- # cider (8)
- # circleci (45)
- # clj-kondo (5)
- # cljs-dev (25)
- # cljsrn (5)
- # clojure (116)
- # clojure-europe (10)
- # clojure-nl (18)
- # clojure-uk (14)
- # clojuredesign-podcast (6)
- # clojurescript (50)
- # cursive (12)
- # data-science (8)
- # datomic (8)
- # duct (39)
- # emacs (6)
- # fulcro (21)
- # graalvm (12)
- # kaocha (17)
- # off-topic (184)
- # pathom (1)
- # pedestal (2)
- # re-frame (31)
- # reagent (24)
- # reitit (1)
- # sci (1)
- # shadow-cljs (23)
- # sql (147)
- # tools-deps (8)
- # vrac (3)
- # xtdb (35)
Please, for the love of all that is holy, if something is runtime dynamic, pass it around.
I was profiling my clojure app with JFR and noticed that most of the allocations are for java.lang.reflect.Method
Is that something you would consider unusual?
It says that bunch of them is coming from clojure.data.csv.write-csv*
- line 125:
(defn- write-csv*
[^Writer writer records sep quote quote? ^String newline]
(loop [records records]
(when-first [record records]
(write-record writer record sep quote quote?)
(.write writer newline)
(recur (next records))))) ; this is the line 125
How can I find what's happening here and is there something I could do to "fix" it?@jumar As a first step, I'd add (set! *warn-on-reflection* true)
to each and every file in your project, just after the ns
form and see what reflection warnings pop up when try to run the code.
I tried that in the repl and then eval data.csv ns but I guess that's too naive. I'll try to call the function
https://clojure.wladyka.eu/posts/how-to-improve-algorithm-performance/#avoid-reflections
(set! *warn-on-reflection* true)
(require 'main-namespace :reload-all)
you can also try thishmm I am curious if there is some deps.edn
which I can use to validate if I have any reflection in system and fail build
(I don't see anything obvious in your code fragment but I suspect line numbers are not always reported entirely accurately)
I haven't used flight recorder, but I don't think you are interpreting what it is showing you correctly
The stacktrace there doesn't contain any Java reflection, therefore it is not a place where method objects are being allocated for reflection
Which also explains why you aren't getting a reflection warning when compiling that code
Interestingly, I got this, BUT only when I tried to instrument the write-csv* function with Cider debugger:
Reflection warning, /Users/jumar/.m2/repository/org/clojure/data.csv/1.0.0/data.csv-1.0.0.jar:clojure/data/csv.clj:125:7 - call to method write on java.io.Writer can't be resolved (argument types: unknown).
.write
on a Writer
with a String
argument seems unambiguous -- am I missing something?
I'll try to manually call the real function which under the hood uses clojure.data.csv (it's a bit harder); now I just tried to call write-csv by hand. Maybe the stacktrace from JFR is indeed misleading I'm just curious what those 195 GB come from 🙂
If you set warn on reflection in all your namespaces you will get a warning printed out anytime the compiler generates a reflective call
https://github.com/clojure/data.csv/blob/master/src/main/clojure/clojure/data/csv.clj#L15 is a commented out example of doing that
No, it's per-file.
Setting it in your main namespace is usually too late (everything is already compiled by the time the set! Is run)
Setting it in the repl can work, but you have to really aware of when you are loading code and set it before you load anything
There’s also https://github.com/athos/clj-check for those using tools.deps.
why is there an API to change the root value of a var, but not to only inspect it without interop: (.getRawRoot ...)
?
anybody here that has used Electron framework to bundle a Clojure/ClojureScript webapp?
what's the question behind the question?
Where can I remind myself of the details of false being true in clojure if something uses the "wrong" java.Lang.Boolean methods?
public Object eval() {
Object t = testExpr.eval();
if(t != null && t != Boolean.FALSE)
return thenExpr.eval();
return elseExpr.eval();
}
no, clojure uses a shortcut where if is just an identity check
java actually compares values
(ins)user=> (boolean? (Boolean. "TRUE"))
true
(ins)user=> (= true (Boolean. "TRUE"))
true
(ins)user=> (boolean? (Boolean. "FALSE"))
true
(ins)user=> (= false (Boolean. "FALSE"))
true
(ins)user=> (if (Boolean. "FALSE") :yes :no)
:yes
the compiler eval method call is basically never called. the eval methods in the compiler are kind of vestigial. they are called in a very limited set of cases when running code in the repl can skip generating bytecode, the majority of code in the repl and otherwise is compiled to bytecode which doesn't use the eval methods in the compiler
I think I need to learn how java.io.Serialization works, because I'm pretty sure that a round trip through that with a Boolean caused me grief
@hiredman meaning the example i posted is never called? public static class IfExpr implements Expr, MaybePrimitiveExpr{
?
it may be called for some limited number of trivial expressions when evaluating code in the repl, but for the most part the eval methods are vestigial
ok. so the check is actually
gen.getStatic(BOOLEAN_OBJECT_TYPE, "FALSE", BOOLEAN_OBJECT_TYPE);
gen.visitJumpInsn(IF_ACMPEQ, falseLabel);
Here is one place with some details and background where this is documented: http://clojuredocs.org/clojure.core/if
Feel free to edit to add more details for future reference to folks interested in the implementation details, if you wish.
user=> (let [fos (java.io.FileOutputStream. "/tmp/t") oos (java.io.ObjectOutputStream. fos)] (.writeObject oos false) (.close oos))
nil
user=> (let [fis (java.io.FileInputStream. "/tmp/t") ois (java.io.ObjectInputStream. fis)] (def x? (.readObject ois)) (.close ois))
nil
user=> x?
false
user=> (if x? :was_true :was_false)
:was_true
This is indirectly what ruined my day :) Except I didn't know what serialization quartz was using.serialization libraries seem to be a semi-common source of introducing freshly constructed Boolean objects.
In "The Joy of Clojure" 2nd edition, there is an interesting discussion on the perils of using Java's Boolean class in section 3.1.2 "Don't create Boolean objects"
As noted in the just mentioned section, the right way to parse a boolean value is this:
(if (Boolean/valueOf "false")
:truthy
:falsey)
;=> :falsey
Hmm, this https://twitter.com/practical_li/status/1276481899045797889 got me thinking, that is there a bash/zsh completions, that would fill the aliases for the Clojure CLI (like clj -A:re<TAB>
-> clj -A:rebel
)?
Does anyone have a durable, distributed, job scheduler that runs in the jvm? I'm using quartz now, but I'm reevaluating. I'd prefer if it was durable to jdbc like quartz.
Can someone tell me why
(defn using-pmap
[update-fn coll]
(dorun (pmap update-fn coll)))
is significantly faster than
(defn using-async
[n update-fn coll]
(let [coll-ch (a/chan n)
_ (a/onto-chan! coll-ch coll)]
(let [processed-ch (-> (repeatedly
n
#(a/go-loop [item (<! coll-ch)]
(if item
(do
(update-fn item)
(recur (<! coll-ch)))
:done)))
doall)]
(doseq [ch processed-ch]
(<!! ch))
nil)))
https://eli.thegreenplace.net/2017/clojure-concurrency-and-blocking-with-coreasync/ is a good read. blocking io and go blocks don't mix, you should be doing blocking io on a dedicated thread and not a dispatch thread. async/thread will run work on an independent thread and return a channel that you can use to still participate in coordination activities
core.async is sort of similar to an event loop. go blocks are used for "select" loop activities and threads are used for event handling. the one exception being if your event handlers are cpu bound then it doesn't make much difference whether you run it on a dispatch thread or a dedicated thread because either way the cpu is busy. blocking io is important to differentiate and separate because it ties up a thread but doesn't tie up the cpu
(this is how i've come to understand it at least, though i am not an expert)
Thanks that analogy with event loop is very useful
go is rarely a performance optimization, its primary purpose is to make it harder to implement concurrency bugs
if you don't need coordination between threads, core.async won't help you much
The CPU for pmap goes beyond 200% while the async one stays put at 40%
for starters, all go blocks share a small number of threads that doesn't expand
if your work has a significant IO element, the go blocks simply starve each other without increasing CPU usage
Does it expand for pmap, I thought pmap was also something like number of cores + 2 threads
which is the same number shared by all go blocks, not just the ones you created in your demo
and their coordination is more expensive than that done by pmap
Yes it involves significant IO
you're paying for features you aren't using in the core.async case
pmap is limitted, but that limit doesn't compose well with some features of lazy-seqs which can result in going past the limit
@hiredman mind expanding a bit?
The going past the limit part
lazy-seqs are really "not strict seqs" meaning sometimes you can realize more than the next element you asked for
If not pmap what else, write my own parallel way to process the coll?
pmap uses laziness to control thread count, rather than using a pool
That is fine in my case, I am using a dorun anyway
@frozenfire1992 I've had great luck with claypoole, and often the underlying ExecutorService which comes with the vm is enough
Yeah chunking I know, will try to chunk my coll and see if it improves perf
That’s trying to make way too many threads
you almost always want access to the executor and to do things in a finer grained way
@noisesmith Thanks will look into it 👍
the other issue with building your parallelism on top of seqs is your are limiting the concurrency by imposing order
Yeah I guess the pmap threads have to wait for the other ones to finish and then move forward in the seq
cool will check it out, thanks a lot
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorCompletionService.html
it depends on what the "update-fn" is doing (you might be blocking threads without knowing) @frozenfire1992
The update-fn in my actual code is for making some db writes and calling an API endpoint
But I did test it with simple and heavy calculations without any IO and pmap still performed significantly better
Also in my actual code I had the update-fn wrapped in (<! (a/thread (update-fn item)))
I guess I shouldnt be doing such heavy IO in a go block because of core.async coordinate the parking and bringing the processes back