Hello wonderful people! Can anyone share some pointers on how to properly profile and debug bb scripts? I am talking here more babashka specific approaches or pointers. I can run basic profiling on OS side, but interested if maybe people already spent some time and have something to share. For context, I am doing some yak-shaving and migrating some personal tooling scripts from the java -> clojure -> babashka. There is some expected marginal increase in execution time, but also some quite unexpected (and not so marginal) increases. My current "rule of assesment" is that I expect bb to be slower for big data wrangling tasks. So for example: I have a "sameish" script run across 3 "envs" java is ~100ms, clojure is 1 second, and babashka is 1.5 second. This is working timings for me no complaints so far ๐. Script itself is quite simple read file from disk compute sha256 digest and output it back. But for the same script when I increase the input size of file from 2MB to 200MB babashka script runtime is in multiple of minutes. And I see two possible solutions here I need to update my "rule" or I need to profile the babashka script executions or something else is happening that I missing. So will be happy for any feedback here. bb script in question is this:
(ns scripts
(:require [ :as io]))
(defn file-to-a-digest
[file]
(let [md (java.security.MessageDigest/getInstance "SHA-256")
digest-arr (with-open [s (io/input-stream file :buffer-size 8192)
dis (new java.security.DigestInputStream s md)]
(while (not= (.read dis) -1))
(.digest (.getMessageDigest dis)))]
digest-arr))
(.read ...) on every character is very expensive in babashka since it's java interop. if you could come up with a clojure stdlib function that did the same, then it would be in the same ballpark
I can probably optimize .read though
Try this instead:
(ns scripts
(:require [ :as io]))
(defn file-to-a-digest
[file]
(let [md (java.security.MessageDigest/getInstance "SHA-256")]
(with-open [s (io/input-stream file :buffer-size 8192)
dis (java.security.DigestInputStream. s md)
out (java.io.OutputStream/nullOutputStream)]
(io/copy dis out :buffer-size 8192)
(.digest md)))) Yep. The copy buffer approach is significantly faster in bb. Thank you. Will add it to the heap of stuff to be aware for the bb interop scripts.
interop in a hot loop is just much slower than in a JVM, that's the rule of thumb
And just like that you singlehandedly reduced my yak-shaving time ๐ . Thank you.
https://gist.github.com/borkdude/a19d6f12a0292deaf489d1eeb6374597 https://gist.github.com/borkdude/a19d6f12a0292deaf489d1eeb6374597 Feedback welcome. Text isn't intended for publication, so it's not about that, just the technical side of it.
I often use bb just as a launcher for (clojure -X:build ...) I wonder if a cool feature of bb tasks would be you can tell it to auto-map all build.clj public functions to a build task of the same name. For example I have:
{:tasks
{test (clojure "-X:build test")
test-vars {:doc "Run specific tests by passing their fully qualified names"
:task (do (println "Testing with core.async 1.8+")
(apply clojure "-M:test" "-v" *command-line-args*)
(println "Now testing with core.async 1.7")
(apply clojure "-M:test:test-1.7" "-v"
*command-line-args*))}
gen (clojure "-X:build gen")
lint (clojure "-X:build lint")
deploy (clojure "-X:build deploy")
install (clojure "-X:build install")
update-documents (clojure "-X:build update-documents")
bump-major-version (clojure "-X:build bump-major-version")
bump-minor-version (clojure "-X:build bump-minor-version")
bump-patch-version (clojure "-X:build bump-patch-version")
release (clojure "-X:build release")
publish (clojure "-X:build publish")
publish-patch (clojure "-X:build publish-patch")
publish-minor (clojure "-X:build publish-minor")
publish-major (clojure "-X:build publish-major")}}
Maybe if I could do:
{:tasks
:all-from-build-clj true
{test-vars {:doc "Run specific tests by passing their fully qualified names"
:task (do (println "Testing with core.async 1.8+")
(apply clojure "-M:test" "-v" *command-line-args*)
(println "Now testing with core.async 1.7")
(apply clojure "-M:test:test-1.7" "-v"
*command-line-args*))}}}Though it might break shell completion, and I wouldn't want to lose that
You may be able to write a rewrite clj script that adds them or using rewrite-edn
Perhaps in combination with clj kondo analysis
โ ๏ธ ๐ Here is a https://github.com/babashka/babashka/tree/cli-tree-tasks/test-cli of a feature that will hopefully soon land in bb. It's auto-completions for bb tasks powered by #C03KJHCNZ99
It auto-completes options / arguments and offers automatic --help.
The prose in this link is crappy, that's not what this is about. I'm only interested in feedback on how it works. There's a test binary link at the end of the document.
Reactions in ๐งต