{"id":13546359,"url":"https://github.com/guicho271828/inlined-generic-function","last_synced_at":"2025-04-02T18:30:36.699Z","repository":{"id":151237062,"uuid":"48306994","full_name":"guicho271828/inlined-generic-function","owner":"guicho271828","description":"Bringing the speed of Static Dispatch to CLOS. Succeeded by https://github.com/marcoheisig/fast-generic-functions","archived":true,"fork":false,"pushed_at":"2019-04-29T01:34:35.000Z","size":72,"stargazers_count":108,"open_issues_count":4,"forks_count":6,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-11-03T14:35:37.738Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/guicho271828.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-20T04:19:14.000Z","updated_at":"2023-10-02T12:21:01.000Z","dependencies_parsed_at":null,"dependency_job_id":"d52d2681-648a-451a-abd8-e57e11ad5fd4","html_url":"https://github.com/guicho271828/inlined-generic-function","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guicho271828%2Finlined-generic-function","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guicho271828%2Finlined-generic-function/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guicho271828%2Finlined-generic-function/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/guicho271828%2Finlined-generic-function/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/guicho271828","download_url":"https://codeload.github.com/guicho271828/inlined-generic-function/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246869602,"owners_count":20847158,"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":[],"created_at":"2024-08-01T12:00:35.850Z","updated_at":"2025-04-02T18:30:36.693Z","avatar_url":"https://github.com/guicho271828.png","language":"Common Lisp","readme":"\n* Bringing the speed of Static Dispatch to CLOS --- Inlined-Generic-Function [[https://circleci.com/gh/guicho271828/inlined-generic-function][https://circleci.com/gh/guicho271828/inlined-generic-function.svg?style=svg]] [[https://travis-ci.org/guicho271828/inlined-generic-function][https://travis-ci.org/guicho271828/inlined-generic-function.svg?branch=master]] [[http://quickdocs.org/inlined-generic-function/][http://quickdocs.org/badge/inlined-generic-function.svg]] \n\nGeneric functions are convenient but slow.  During the development we\nusually want the full dynamic feature of CLOS. However, when we really need\na fast binary and do not need the dynamism, the dynamic dispatch in the\ngeneric functions should be statically compiled away.\n\nWe propose a MOP-based implementation of fast inlined generic functions\ndispatched in compile-time. The amount of work required to inline your\ngeneric function is minimal. \n\nEmpirical analysis showed that *the resulting code is up to 10 times\nfaster than the standard generic functions.*\n\nTested on SBCL and CCL.\n\n* News\n\nThanks to the suggestion from *@phmarek*,\nI decided to add a feature called =invalid-branch= which uses impl-specific\nfeature to *enable a static type checking*.\n[[./invalid-branch.org][Read the doc!]]\n\n*Added an additional usage note.*\n\n* Usage\n\nThe example code here is in =t/playground.lisp=.\n\nFirst, declare the generic function with =inlined-generic-function=\nmetaclass.  This metaclass is a subclass of\n=standard-generic-function=. Therefore, unless you use its special feature,\nit acts exactly the same as the normal generic functions.\n\n#+BEGIN_SRC lisp\n(defgeneric plus (a b)\n  (:generic-function-class inlined-generic-function))\n#+END_SRC\n\nDefine the methods as usual.\n\n#+BEGIN_SRC lisp\n(defmethod plus :around ((a number) (b number))\n  (+ a b) ;; not a meaningful operation...\n  (call-next-method))\n\n(defmethod plus ((a fixnum) (b fixnum))\n  (+ a b))\n(defmethod plus ((a float) (b float))\n  (+ a b))\n#+END_SRC\n\nDefine a function which uses it.\n\n#+BEGIN_SRC lisp\n(defun func-using-plus (a b)\n  (plus a b))\n#+END_SRC\n\nAt this point the gf is not inlined.\n\n#+BEGIN_SRC lisp\n; disassembly for FUNC-USING-PLUS\n; Size: 24 bytes. Origin: #x100A75A165\n; 65:       488BD1           MOV RDX, RCX                     ; no-arg-parsing entry point\n; 68:       488BFB           MOV RDI, RBX\n; 6B:       488B059EFFFFFF   MOV RAX, [RIP-98]                ; #\u003cFDEFINITION for PLUS\u003e\n; 72:       B904000000       MOV ECX, 4\n; 77:       FF7508           PUSH QWORD PTR [RBP+8]\n; 7A:       FF6009           JMP QWORD PTR [RAX+9]\n#+END_SRC\n\nNow its time to inline the gf. There's nothing different from inlining a normal function.\nIn order to inline the generic function, just declare it =inline= when you use it.\n\n#+BEGIN_SRC lisp\n(defun func-using-plus (a b)\n  (declare (inline plus))\n  (plus a b))\n; disassembly for FUNC-USING-INLINED-PLUS\n; Size: 323 bytes. Origin: #x1002C3BD45\n; D45:       8D41F1           LEA EAX, [RCX-15]               ; no-arg-parsing entry point\n; D48:       A80F             TEST AL, 15\n; D4A:       755F             JNE L2\n; .....\n#+END_SRC\n\nTo see the actual compiler-macro expansion, use a function =inline-generic-function=.\n\n#+BEGIN_SRC lisp\n(let ((*features* (cons :inline-generic-function *features*)))\n  (print (inline-generic-function '(plus a b))))\n\n;; Inlining a generic function PLUS\n\n(LET ((#:A1734 (1+ A)) (#:B1735 (1- B)))\n  (EMATCH* (#:A1734 #:B1735)\n    (((TYPE FLOAT) (TYPE FLOAT))\n     (LET ((A #:A1734) (B #:B1735))\n       (DECLARE (TYPE FLOAT A))\n       (DECLARE (TYPE FLOAT B))\n       (+ A B)\n       (LET ((A #:A1734) (B #:B1735))\n         (DECLARE (TYPE FLOAT A))\n         (DECLARE (TYPE FLOAT B))\n         (+ A B))))\n    (((TYPE FIXNUM) (TYPE FIXNUM))\n     (LET ((A #:A1734) (B #:B1735))\n       (DECLARE (TYPE FIXNUM A))\n       (DECLARE (TYPE FIXNUM B))\n       (+ A B)\n       (LET ((A #:A1734) (B #:B1735))\n         (DECLARE (TYPE FIXNUM A))\n         (DECLARE (TYPE FIXNUM B))\n         (+ A B))))))\n#+END_SRC\n\nSince =ematch= from Trivia pattern matcher expands into thoroughly typed\ndispatching code, a sufficiently smart compiler would compile =+= into\nmachine assembly, which is the case at least in SBCL.\n\n** Automatic compile-time dispatching\n\nIf the code is inlined in a typed environment, smart compilers like sbcl can\ndetect certain branches are not reachable, thus removing the checks and\nreducing the code size. This is equivalent to compile-time dispatch.\nIn the example below, the code for dispatching\nFLOAT is removed.\n\n#+BEGIN_SRC lisp\n(defun func-using-inlined-plus-and-type-added (a b)\n  \" ; disassembly for FUNC-USING-INLINED-PLUS-AND-TYPE-ADDED\n; Size: 29 bytes. Origin: #x10031E7788\n; 88:       4801F9           ADD RCX, RDI                     ; no-arg-parsing entry point\n; 8B:       488BD1           MOV RDX, RCX\n; 8E:       48D1E2           SHL RDX, 1\n; 91:       710C             JNO L0\n; 93:       488BD1           MOV RDX, RCX\n; 96:       41BB70060020     MOV R11D, 536872560              ; ALLOC-SIGNED-BIGNUM-IN-RDX\n; 9C:       41FFD3           CALL R11\n; 9F: L0:   488BE5           MOV RSP, RBP\n; A2:       F8               CLC\n; A3:       5D               POP RBP\n; A4:       C3               RET\n\"\n  (declare (inline plus))\n  (declare (optimize (speed 3) (safety 0)))\n  (declare (type fixnum a b))\n  (plus a b))\n#+END_SRC\n\nIf the types does not match, errors are signalled by =EMATCH=, \nwhich is consistent with the behavior of standard generic functions.\n\n** Enabling Inlining Globally\n\nInlining is not globally enabled by default.\nThis is because the inlined code becomes obsoleted when the\ngeneric function definition changes, and therefore\nyou generally do not want to make them inlined during the development.\n\nIt can be enabled globally \nby adding =:inline-generic-function= flag in\n=*features*=, which is useful when you build a standalone binary.\nWhen this feature is present, all inlinable generic functions\nare inlined unless it is declared =notinline=.\n\n#+BEGIN_SRC lisp\n(push :inline-generic-function *features*)\n#+END_SRC\n\n** Benchmark Setting\n\nWe tested two generic functions, one of which is a\nstandard-generic-function, and another is an inlined-generic-function.\n\nBoth generic functions follow the definition below:\n\n#+BEGIN_SRC lisp\n(defgeneric plus (a b)\n  [(:generic-function-class inlined-generic-function)])\n(defmethod plus :around ((a number) (b number))\n  (+ a b)\n  (call-next-method))\n(defmethod plus ((a fixnum) (b fixnum))\n  (+ a b))\n(defmethod plus ((a double-float) (b double-float))\n  (+ a b))\n#+END_SRC\n\nWe tested them with and without =inline= declaration, i.e., \n\n#+BEGIN_SRC lisp\n(defun func-using-plus (a b)\n  (declare (optimize (speed 3) (safety 0)))\n  (plus a b))\n\n(defun func-using-inlined-plus (a b)\n  (declare (inline plus))\n  (declare (optimize (speed 3) (safety 0)))\n  (plus a b))\n#+END_SRC\n\nThus, we have 4 configurations in total.  The experiment is run under AMD\nPhenom II X6 processor 2.8GHz with SBCL 1.3.1 (launched by Roswell).\nThe benchmark function is shown below:\n\n#+BEGIN_SRC lisp\n(defvar *input* (iter (repeat 1000)\n                     (collect (cons (random 100.0d0) (random 100.0d0)))\n                     (collect (cons (+ 20 (random 100)) (+ 20 (random 100))))))\n(defun benchmark ()\n  (time (iter (for (a . b) in *input*)\n              (func-using-normal-plus a b)))\n  (time (iter (for (a . b) in *input*)\n              (func-using-normal-inlined-plus a b)))\n  (time (iter (for (a . b) in *input*)\n              (func-using-plus a b)))\n  (time (iter (for (a . b) in *input*)\n              (func-using-inlined-plus a b))))\n#+END_SRC\n\nWe first run the benchmark function 1000 times in order to calibrate the CPU cache.\nWe then run the gc and invoke the benchmark function once more.\nWe use the result of this final run in order to make sure the machine state is stabilized.\n\n** Result\n\nSince the difference in the runtime is relatively small due to the small\namount of computation, we consider the processor cycles only.  We found\nthat the cost of generic function invocation is considerably low when an\n=inlined-generic-function= is invoked with =inline= declaration.\n\n| metaclass and inline declaration       | processor cycles | consing |\n|----------------------------------------+------------------+---------|\n| standard-generic-function, not inlined |          742,285 |       0 |\n| standard-generic-function, inlined     |          726,023 |       0 |\n| inlined-generic-function, not inlined  |        7,865,080 | 523,760 |\n| inlined-generic-function, inlined      |         *74,120* |       0 |\n\nNote that the third case, where the =inlined-generic-function= is not\ninlined, is slower than the normal generic function. This would be because\nwe use the non-standard metaclass for representing the generic function and\nthe normal optimization provided by the implementation is not performed.\nHowever, this is not a problem because we consider the third case only takes\nplace during the development.\n\n** Conclusion\n\nWe showed that ... well, anyway, this is not a paper. Enjoy!\n\n** Dependencies\n\nThis library is at least tested on implementation listed below:\n\n+ SBCL 1.3.1 on X86-64 Linux  3.19.0-39-generic (author's environment)\n\nAlso, it depends on the following libraries:\n\n+ trivia by Masataro Asai ::\n    NON-optimized pattern matcher compatible with OPTIMA, with extensible optimizer interface and clean codebase\n\n+ closer-mop by Pascal Costanza ::\n    Closer to MOP is a compatibility layer that rectifies many of the absent or incorrect CLOS MOP features across a broad range of Common Lisp implementations.\n\n+ alexandria by  ::\n    Alexandria is a collection of portable public domain utilities.\n\n+ iterate by  ::\n    Jonathan Amsterdam's iterator/gatherer/accumulator facility\n\n\n* Usage Note\n\nWhen you use this library as part of your system, *make sure that the method\ndefinitions are re-evaluated in load-time*. This is necessary because the\ninlining information for the method could be lost after the compilation, i.e.,\nthe FASL file does not keep the defmethod form that should be inlined later.\n\nThe only thing you need is:\n\n#+BEGIN_SRC lisp\n(eval-when (:compile-toplevel :load-toplevel :execute)\n  (defmethod ...)\n  ...)\n#+END_SRC\n\n* Installation\n\nQuicklisp available.\n\n** Author\n\n+ Masataro Asai (guicho2.71828@gmail.com)\n\n* Copyright\n\nCopyright (c) 2015 Masataro Asai (guicho2.71828@gmail.com)\n\n\n* License\n\nLicensed under the LLGPL License.\n\n\n\n","funding_links":[],"categories":["Common Lisp","Miscellaneous ##"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguicho271828%2Finlined-generic-function","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fguicho271828%2Finlined-generic-function","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fguicho271828%2Finlined-generic-function/lists"}