{"id":13609451,"url":"https://github.com/bsless/clj-fast","last_synced_at":"2025-04-04T18:06:21.802Z","repository":{"id":42067556,"uuid":"216785313","full_name":"bsless/clj-fast","owner":"bsless","description":"Unpredictably faster Clojure","archived":false,"fork":false,"pushed_at":"2022-04-14T08:13:41.000Z","size":4610,"stargazers_count":243,"open_issues_count":10,"forks_count":1,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-04T14:07:04.007Z","etag":null,"topics":["clojure","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-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bsless.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-10-22T10:22:19.000Z","updated_at":"2025-02-07T11:28:47.000Z","dependencies_parsed_at":"2022-08-12T04:00:24.779Z","dependency_job_id":null,"html_url":"https://github.com/bsless/clj-fast","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsless%2Fclj-fast","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsless%2Fclj-fast/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsless%2Fclj-fast/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsless%2Fclj-fast/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bsless","download_url":"https://codeload.github.com/bsless/clj-fast/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247226213,"owners_count":20904465,"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":["clojure","performance"],"created_at":"2024-08-01T19:01:35.060Z","updated_at":"2025-04-04T18:06:21.784Z","avatar_url":"https://github.com/bsless.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"[![Clojars Project](https://img.shields.io/clojars/v/bsless/clj-fast.svg)](https://clojars.org/bsless/clj-fast)\n[![cljdoc badge](https://cljdoc.org/badge/bsless/clj-fast)](https://cljdoc.org/d/bsless/clj-fast/CURRENT)\n[![CircleCI](https://circleci.com/gh/bsless/clj-fast/tree/master.svg?style=shield)](https://circleci.com/gh/bsless/clj-fast/tree/master)\n\n\u003c!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --\u003e\n**Table of Contents**\n\n- [clj-fast](#clj-fast)\n    - [Purpose](#purpose)\n    - [Latest Version](#latest-version)\n    - [Results](#results)\n    - [Usage](#usage)\n        - [Requirements](#requirements)\n        - [Functions and Macros](#functions-and-macros)\n            - [Fast(er) Functions](#faster-functions)\n            - [Inline Macros](#inline-macros)\n                - [Notes](#notes)\n                - [Additions](#additions)\n            - [Bypass dynamic dispatch with type hints](#bypass-dynamic-dispatch-with-type-hints)\n            - [Collections](#collections)\n                - [HashMap](#hashmap)\n                - [ConcurrentHashMap](#concurrenthashmap)\n            - [Lenses](#lenses)\n    - [Rewriting Core Functions And Macros](#rewriting-core-functions-and-macros)\n        - [Usage](#usage-1)\n    - [General Note Note On Performance And Profiling](#general-note-note-on-performance-and-profiling)\n    - [Related Projects](#related-projects)\n        - [Structural](#structural)\n        - [Stringer](#stringer)\n    - [License](#license)\n    - [Credit](#credit)\n\n\u003c!-- markdown-toc end --\u003e\n# clj-fast\n\nLibrary for playing around with low level Clojure code for performance\nreasons given some assumptions. Inspired by [Naked Performance (with\nClojure) – Tommi Reiman](https://www.youtube.com/watch?v=3SSHjKT3ZmA).\n\nSome of the code is based on implementations in metosin's projects. Credit in code.\n\n## Purpose\n\nThis repo serves a dual purpose:\n\n- Providing faster implementations of Clojure's core functions as\n  macros.\n- Reference guide on the performance characteristics of different ways\n  of using Clojure's data structures.\n\nWhat makes it possible?\n\nPlenty of Clojure's core functions are implemented to be generic (good)\nand to accept a variable number of arguments (also very good). The\nproblem is that we pay for this in performance. Wherever we iterate over\na sequence of input arguments or dispatch on the class, we lose on\nperformance, especially when iterating on arguments and calling `next`,\n`more` or `rest` repeatedly.\n\nPlenty of these behaviors are just forms of flow-control, and like `and`\nand `or`, other forms of flow control can too be statically analyzed,\nunder certain constraints, and replaced by faster code.\n\n## Latest Version\n\n[![Clojars Project](http://clojars.org/bsless/clj-fast/latest-version.svg)](http://clojars.org/bsless/clj-fast)\n\n## Results\n\nSee [results.md](extra/clj-fast.analysis/doc/results.md) for experiments' detailed benchmark results.\n\n## Usage\n\n### Requirements\n\nAdd in your `project.clj`:\n\n```clojure\n[bsless/clj-fast \"0.0.10\"]\n```\n\nWARNING: Due to a bug in leiningen, versions built prior to `0.0.9` will pull in extra dependencies. Make sure to upgrade!\n\n### Functions and Macros\n\n#### Fast(er) Functions\n\n```clojure\n(require '[clj-fast.core :as fast])\n```\n\n- `entry-at`: used like `find` but doesn't dispatch and has inline\n  definition. Works for `IPersistentMap`.\n- `val-at`: used like `get` but doesn't dispatch and has inline\n  definition. Works for `IPersistentMap`.\n- `fast-assoc`: Used like `assoc` but doesn't take variable key-values,\n  only one pair and has inline definition. Works on `Associative`.\n- `fast-map-merge`: Slightly faster version for `merge`, takes only 2\n  maps.\n- `rmerge!`: merges a map into a transient map.\n\n\n#### Inline Macros\n\n```clojure\n(require '[clj-fast.inline :as inline])\n```\n\nLike regular core functions but sequence arguments must be written\nexplicitly for static analysis or `def`ed in advance (i.e. `resolve`-able).\n\nExamples:\n\n```clojure\n(def ks [:a :b])\n\n(inline/assoc m :a 1 :b 2)\n\n(inline/dissoc m :a :b)\n\n(inline/fast-assoc m :a 1 :b 2)\n\n(inline/get-in m ks)\n\n(inline/get-in m [:c :d])\n\n(inline/get-some-in m [:c :d])\n\n(inline/assoc-in m [:c :d] foo)\n\n(inline/assoc-in m [:c :d] foo [:c :b] bar)\n\n(inline/update-in m [:c :d] inc)\n\n(inline/select-keys m [:a :b :c])\n\n(inline/merge m1 m2 m3)\n\n(def assoc* (inline/memoize-c 3 assoc))\n```\n\n##### Notes\n\n- Merge analysis unrolls inline maps as well.\n- Warning: additional arities of assoc-in will cause code reordering.\n  Beware of side effects.\n\n##### Additions\n\n- `fast-assoc`: inlines in the same manner of `assoc` but uses\n  `clj-fast.core/fast-assoc` instead.\n- `fast-map-merge`: inlines in the same manner of `merge` but uses\n  `clj-fast.core/fast-map-merge` instead (Metosin).\n- `fast-select-keys`: like `select-keys`, but faster and dirtier, adds\n  nil entries to the results map.\n- `get-some-in`: Like `get-in` at the expense of working only on callables\n  (objects implementing `clojure.lang.IFn`).\n- `find-some-in`: like `get-some-in` but returns a map-entry in the end,\n  like `find`.\n- `memoize*` \u0026 `memoize-c*`: Alternative implementations for memoization\n  using a nested Clojure hash map and a nested Java concurrent hash map\n  respectively. Fall back to `core/memoize` for large arities. Due to\n  the cost of hashing objects in Clojure, it's recommended to use\n  `memoize-c*` for most use cases.\n\n#### Bypass dynamic dispatch with type hints\n\nThe `get` and `nth` macros operate similarly to their respective\nfunctions with one notable difference: When provided with an appropriate\ntype hint, they will dispatch to the underlying method at compile time\ninstead of run time.\n\n```clojure\n(def arr (long-array [1 2 3]))\n(nth ^longs arr 0)\n(def m (doto (java.util.HashMap.) (.put :a 1)))\n(get ^Map m :a)\n```\n\n#### Collections\n\n##### HashMap\n\n```clojure\n(require '[clj-fast.collections.hash-map :as hm])\n```\n\n- `-\u003ehashmap`: wraps `HashMap`'s constructor.\n- `get`: wraps method call for `HashMap`'s `get`. Has inline definition.\n- `put`: wraps method call for `HashMap`'s `put`. Has inline definition.\n\n##### ConcurrentHashMap\n\n```clojure\n(require '[clj-fast.collections.concurrent-map :as chm])\n```\n\n- `-\u003econcurrent-hash-map`: constructor.\n- `concurrent-map?`: instance check.\n- `put!?`: `putIfAbsent`.\n- `get`\n- `get?`: get if is a concurrent hash map.\n- `get-in?`: like clojure core's get-in but for nested concurrent hash maps.\n- `put-in!`: like clojure core's assoc-in but for nested concurrent hash maps.\n\n#### Lenses\n\n```clojure\n(require '[clj-fast.lens :as lens])\n```\n\nIn typed functional programming, lenses are a generic way of getting\nand setting nested data structures (records).\n\nIn this context, the `lens` namespace implements the basic code structure\nunderlying Clojure's `get-in`, `some-\u003e`, `assoc-in` and `update-in`.\nThey can be used in macros to expand to real code provided an appropriate\n1-depth `get` and/or `put` transformer, which takes arguments and returns\nan expression.\n\nFor example, the `get-some` lens is used to define `inline/get-some-in`:\n\n```clojure\n(defmacro get-some-in\n  [m ks]\n  (lens/get-some (fn [m k] `(~m ~k)) m ks))\n```\n\nSimilarly, for `assoc-in`:\n\n```clojure\n(defmacro assoc-in\n  [m ks v]\n  (lens/put\n   (fn [m k v] `(c/assoc ~m ~k ~v))\n   (fn [m k] `(c/get ~m ~k))\n   m\n   (u/simple-seq ks)\n   v))\n```\n\nSo be careful, these are not functional programming lenses, but\nmetaprogramming lenses used for code generation.\n\n## Rewriting Core Functions And Macros\n\nThe namespace `clj-fast.clojure.core` contains drop-in replacement\nfunctions and macros for Clojure's core.\n\nIt opportunistically replaces functions by their inlined\nimplementations. It also includes binding macros (let, fn, loop, defn)\nwhich will use inlining versions of `get` and `nth` when possible. (i.e.\nwhen type-hinted).\n\n### Usage\n\n```clojure\n(ns com.my.app\n  (:refer-clojure\n   :exclude\n   [get nth assoc get-in merge assoc-in update-in select-keys memoize destructure let fn loop defn defn-])\n  (:require\n   [clojure.core :as c]\n   [clj-fast.clojure.core :refer [get nth assoc get-in merge assoc-in update-in select-keys memoize destructure let fn loop defn defn-]]))\n```\n\n## General Note Note On Performance And Profiling\n\nProfiling and performance measurements on the JVM are not an exact\nscience.\nThe variety of contributing factors and their possible interactions are\nfar from all being accounted for.\n\nStill, one of the most significant factors is the JVM's JIT compiler.\n\nIt is absolutely essential where performance is concerned.\n\nSome tools such as Leiningen suppress the JIT to enable faster start-up\ntimes. While this is desirable in a development environment, it must be\nproperly configured for profiling or production tasks.\n\nThe JVM's configuration settings can be examined by evaluating:\n\n```clojure\n(into [] (.getInputArguments (java.lang.management.ManagementFactory/getRuntimeMXBean)))\n```\n\nIf you see `TieredStopAtLevel=1` or any number below 4 you're\nessentially running without aggressive JIT on.\n\nWith Leiningen, make sure to either use a different profile or override\nthe `:jvm-opts` to get the best performance possible and realistic\nprofiling results.\n\n## Related Projects\n\n### Structural\n\n[Structural](https://github.com/joinr/structural) is a small library by\njoinr (Tom) which provides more efficient destructuring macros with type\nhints.\n\n### Stringer\n\n[Stringer](https://github.com/kumarshantanu/stringer) is a library by\nShantanu Kumar for fast string operations. Of interest are the\ncapabilities it provides in faster string building and formatting, also\nby \"unrolling\" the building operations where statically possible.\n\n## License\n\nCopyright © 2019-2020 ben.sless@gmail.com\n\nCopyright © Rich Hickey for the implementation in `clj-fast.clojure.core`.\n\nThis program and the accompanying materials are made available under the\nterms of the Eclipse Public License 2.0 which is available at\nhttp://www.eclipse.org/legal/epl-2.0.\n\nThis Source Code may also be made available under the following Secondary\nLicenses when the conditions for such availability set forth in the Eclipse\nPublic License, v. 2.0 are satisfied: GNU General Public License as published by\nthe Free Software Foundation, either version 2 of the License, or (at your\noption) any later version, with the GNU Classpath Exception which is available\nat https://www.gnu.org/software/classpath/license.html.\n\n## Credit\n\nCredit to Metosin wherever noted in the code.\n\nRich Hickey for clojure.core ns.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbsless%2Fclj-fast","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbsless%2Fclj-fast","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbsless%2Fclj-fast/lists"}