{"id":28166554,"url":"https://github.com/jgpc42/jmh-clojure","last_synced_at":"2025-05-15T13:13:49.520Z","repository":{"id":62433127,"uuid":"105075525","full_name":"jgpc42/jmh-clojure","owner":"jgpc42","description":"Seamless JMH benchmarking for Clojure","archived":false,"fork":false,"pushed_at":"2025-01-11T13:58:06.000Z","size":296,"stargazers_count":133,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-11T14:44:36.057Z","etag":null,"topics":["benchmarking","clojure","jmh","performance"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jgpc42.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":"jgpc42"}},"created_at":"2017-09-27T22:24:14.000Z","updated_at":"2025-01-11T13:58:10.000Z","dependencies_parsed_at":"2024-01-15T18:45:48.143Z","dependency_job_id":"e858544b-aa71-48af-9b12-ef21c6b0f72e","html_url":"https://github.com/jgpc42/jmh-clojure","commit_stats":{"total_commits":130,"total_committers":2,"mean_commits":65.0,"dds":0.007692307692307665,"last_synced_commit":"c46a3d2ebdc2e6e1e257393a010d125167d05cf0"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgpc42%2Fjmh-clojure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgpc42%2Fjmh-clojure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgpc42%2Fjmh-clojure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgpc42%2Fjmh-clojure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jgpc42","download_url":"https://codeload.github.com/jgpc42/jmh-clojure/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254346569,"owners_count":22055809,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmarking","clojure","jmh","performance"],"created_at":"2025-05-15T13:13:18.752Z","updated_at":"2025-05-15T13:13:49.513Z","avatar_url":"https://github.com/jgpc42.png","language":"Clojure","funding_links":["https://github.com/sponsors/jgpc42"],"categories":[],"sub_categories":[],"readme":"[![Clojars Project](https://img.shields.io/clojars/v/jmh-clojure.svg)](https://clojars.org/jmh-clojure)\n[![](https://github.com/jgpc42/jmh-clojure/workflows/Test%20runner/badge.svg)][ci]\n\n### Dependency and version information\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show\u003c/summary\u003e\n\n[Leiningen][lein]\n\n``` clojure\n[jmh-clojure \"0.4.1\"]\n```\n\n[tools.deps][deps]\n\n```clojure\n{jmh-clojure/jmh-clojure {:mvn/version \"0.4.1\"}}\n```\n\n[Maven][maven]\n\n``` xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ejmh-clojure\u003c/groupId\u003e\n  \u003cartifactId\u003ejmh-clojure\u003c/artifactId\u003e\n  \u003cversion\u003e0.4.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nJDK versions 8 to 18 and Clojure versions 1.7 to 1.11 are currently [tested against][ci].\n\u003c/details\u003e\n\n### What is it?\n\nThis library provides a data-oriented API to [JMH][jmh], the Java Microbenchmark Harness.\n\nJMH is developed by OpenJDK JVM experts and goes to great lengths to ensure accurate benchmarks. Benchmarking on the JVM is a complex beast and, by extension, JMH takes a bit of effort to learn and use properly. That being said, JMH is very robust and configurable. If you are new to JMH, I would recommend browsing the [sample][samples] code and javadocs before using this library.\n\nIf you a need simpler, less strenuous tool, I would suggest looking at the popular [criterium][criterium] library.\n\n### Quick start\n\nAs a simple example, let's say we want to benchmark our fn that gets the value at an arbitrary indexed type. Of course, the built in `nth` already does this, but we can't extend `nth` to existing types like `java.nio.ByteBuffer`, etc.\n\n```clojure\n(ns demo.core)\n\n(defprotocol ValueAt\n  (value-at [x idx]))\n\n(extend-protocol ValueAt\n  clojure.lang.Indexed\n  (value-at [i idx]\n    (.nth i idx))\n  CharSequence\n  (value-at [s idx]\n    (.charAt s idx))\n  #_...)\n```\n\nBenchmarks are [usually](#alternate-ways-to-run) described in data and are fully separated from definitions. The reason for this is twofold. First, decoupling is generally good design practice. And second, it allows us to easily take advantage of JMH process isolation (forking) for reliability and accurracy. More on this later.\n\nFor repeatability, we'll place the following data in a `benchmarks.edn` resource file in our project. (Note that using a file is not a requirement, we could also specify the same data in Clojure. The `:fn` key values would need to be quoted in that case, however.)\n\n```clojure\n{:benchmarks\n [{:name :str, :fn demo.core/value-at, :args [:state/string, :state/index]}\n  {:name :vec, :fn demo.core/value-at, :args [:state/vector, :state/index]}]\n\n :states\n {:index {:fn (partial * 0.5), :args [:param/count]} ;; mid-point\n  :string {:fn demo.utils/make-str, :args [:param/count]}\n  :vector {:fn demo.utils/make-vec, :args [:param/count]}}\n\n :params {:count 10}}\n```\n\nI have omitted showing the `demo.utils` namespace for brevity, it is defined [here][utils] if interested.\n\nThe above data should be fairly easy to understand. It is also a limited view of what can be specified. The [sample file][sample] provides a complete reference and explanation.\n\nNow to run the benchmarks. We'll start a REPL in our project and evaluate the following. Note that we could instead use [lein-jmh][lein-jmh] or one of the other [supported tools](#tooling-support) to automate this entire process.\n\n```clojure\n(require '[jmh.core :as jmh]\n         '[clojure.java.io :as io]\n         '[clojure.edn :as edn])\n\n(def bench-env\n  (-\u003e \"benchmarks.edn\" io/resource slurp edn/read-string))\n\n(def bench-opts\n  {:type :quick\n   :params {:count [31 100000]}\n   :profilers [\"gc\"]})\n\n(jmh/run bench-env bench-opts)\n;; =\u003e ({:name :str, :params {:count 31},     :score [1.44959801438209E8 \"ops/s\"], #_...}\n;;     {:name :str, :params {:count 100000}, :score [1.45485370497829E8 \"ops/s\"]}\n;;     {:name :vec, :params {:count 31},     :score [1.45550038851249E8 \"ops/s\"]}\n;;     {:name :vec, :params {:count 100000}, :score [8.5783753539823E7 \"ops/s\"]})\n```\n\n\u003e **Note:** due to the way jmh-clojure [works][works], the `*compile-path*` directory should exist and be on your classpath before benchmarking. This is automated by tools like Leiningen. For `tools.deps`, see [here][task-cp].\n\nThe `run` fn takes a benchmark environment and an optional map. We select the `:quick` type: an [alias][alias-doc] for some common options. We override our default `:count` parameter sequence to measure our fn against small and large inputs. We also enable the gc profiler.\n\nNotice how we have four results: one for each combination of parameter and benchmark fn. For this example, we have omitted lots of additional result map data, including the [profiler][profilers] information.\n\nNote that the above results were taken from multiple [runs][result], which is always a good practice when benchmarking.\n\n#### Alternate ways to run\n\nBenchmarking expressions or fns manually without the data specification is also supported. For example, the `run-expr` macro provides an interface similar to criterium, and allows benchmarking of code that only resides in memory (that you are updating in a REPL, for example), rather than on disk (loadable via `require`). However, this forgoes JMH process isolation. For more on why benchmarking this way on the JVM can be sub-optimal, see [here][extended].\n\n### Tooling support\n\nThis library can be used directly in a [bare REPL][bare], as shown above, or standalone via [tools.deps][cli]. For a more robust experience, see the [`jmh-clojure-task`][task] project. This companion library provides some additional convenience features like sorting, table output, easy uberjar creation, and more. It can be easily integrated with tools like [Leiningen][lein-jmh].\n\n### More information\n\nAs previously mentioned, please see the [sample file][sample] for the complete benchmark environment reference. For `run` options, see the [docs][run-doc]. Also, see the [wiki][wiki] for additional examples and topics.\n\nThe materials for a talk I gave at a [London Clojurians][london] online meetup are also available [here][talk]. A video capture of the event can also be viewed on [YouTube][video].\n\n### Running the tests\n\n```bash\nlein test\n```\n\nOr, `lein test-all` for all supported Clojure versions.\n\n### License\n\nCopyright © 2017-2025 Justin Conklin\n\nDistributed under the Eclipse Public License, the same as Clojure.\n\n\n\n[alias-doc]:  https://jgpc42.github.io/jmh-clojure/doc/jmh.option.html#var-*type-aliases*\n[bare]:       https://github.com/jgpc42/jmh-clojure/wiki/Bare-REPL\n[ci]:         https://github.com/jgpc42/jmh-clojure/blob/master/.github/workflows/test.yml\n[cli]:        https://github.com/jgpc42/jmh-clojure/wiki/Clojure-1.9-CLI\n[criterium]:  https://github.com/hugoduncan/criterium\n[deps]:       https://github.com/clojure/tools.deps.alpha\n[extended]:   https://github.com/jgpc42/jmh-clojure/wiki/Extended\n[jmh]:        https://github.com/openjdk/jmh\n[lein]:       http://github.com/technomancy/leiningen\n[lein-jmh]:   https://github.com/jgpc42/lein-jmh\n[london]:     https://www.meetup.com/London-Clojurians/\n[maven]:      http://maven.apache.org\n[profilers]:  https://github.com/jgpc42/jmh-clojure/wiki/JMH-Profilers\n[result]:     https://gist.github.com/jgpc42/4d8a828f8d0739748afa71035f2b2c9c#file-results-edn\n[run-doc]:    https://jgpc42.github.io/jmh-clojure/doc/jmh.core.html#var-run\n[sample]:     https://github.com/jgpc42/jmh-clojure/blob/master/resources/sample.jmh.edn\n[samples]:    https://github.com/openjdk/jmh/tree/41548a7/jmh-samples/src/main/java/org/openjdk/jmh/samples\n[talk]:       https://github.com/jgpc42/london-clojurians-jmh-talk-2020\n[task]:       https://github.com/jgpc42/jmh-clojure-task\n[task-cp]:    https://github.com/jgpc42/jmh-clojure-task#usage\n[utils]:      https://gist.github.com/jgpc42/4d8a828f8d0739748afa71035f2b2c9c#file-utils-clj\n[video]:      https://www.youtube.com/watch?v=_6qVfFkBdWI\n[wiki]:       https://github.com/jgpc42/jmh-clojure/wiki\n[works]:      https://github.com/jgpc42/jmh-clojure/wiki/How-It-Works\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjgpc42%2Fjmh-clojure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjgpc42%2Fjmh-clojure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjgpc42%2Fjmh-clojure/lists"}