• This repository has been archived on 28/Apr/2020
  • Stars
    star
    109
  • Rank 319,077 (Top 7 %)
  • Language
    Common Lisp
  • Created almost 9 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Bringing the speed of Static Dispatch to CLOS. Succeeded by https://github.com/marcoheisig/fast-generic-functions

Bringing the speed of Static Dispatch to CLOS โ€” Inlined-Generic-Function https://circleci.com/gh/guicho271828/inlined-generic-function.svg?style=svg https://travis-ci.org/guicho271828/inlined-generic-function.svg?branch=master http://quickdocs.org/badge/inlined-generic-function.svg

Generic functions are convenient but slow. During the development we usually want the full dynamic feature of CLOS. However, when we really need a fast binary and do not need the dynamism, the dynamic dispatch in the generic functions should be statically compiled away.

We propose a MOP-based implementation of fast inlined generic functions dispatched in compile-time. The amount of work required to inline your generic function is minimal.

Empirical analysis showed that the resulting code is up to 10 times faster than the standard generic functions.

Tested on SBCL and CCL.

News

Thanks to the suggestion from @phmarek, I decided to add a feature called invalid-branch which uses impl-specific feature to enable a static type checking. Read the doc!

Added an additional usage note.

Usage

The example code here is in t/playground.lisp.

First, declare the generic function with inlined-generic-function metaclass. This metaclass is a subclass of standard-generic-function. Therefore, unless you use its special feature, it acts exactly the same as the normal generic functions.

(defgeneric plus (a b)
  (:generic-function-class inlined-generic-function))

Define the methods as usual.

(defmethod plus :around ((a number) (b number))
  (+ a b) ;; not a meaningful operation...
  (call-next-method))

(defmethod plus ((a fixnum) (b fixnum))
  (+ a b))
(defmethod plus ((a float) (b float))
  (+ a b))

Define a function which uses it.

(defun func-using-plus (a b)
  (plus a b))

At this point the gf is not inlined.

; disassembly for FUNC-USING-PLUS
; Size: 24 bytes. Origin: #x100A75A165
; 65:       488BD1           MOV RDX, RCX                     ; no-arg-parsing entry point
; 68:       488BFB           MOV RDI, RBX
; 6B:       488B059EFFFFFF   MOV RAX, [RIP-98]                ; #<FDEFINITION for PLUS>
; 72:       B904000000       MOV ECX, 4
; 77:       FF7508           PUSH QWORD PTR [RBP+8]
; 7A:       FF6009           JMP QWORD PTR [RAX+9]

Now its time to inline the gf. Thereโ€™s nothing different from inlining a normal function. In order to inline the generic function, just declare it inline when you use it.

(defun func-using-plus (a b)
  (declare (inline plus))
  (plus a b))
; disassembly for FUNC-USING-INLINED-PLUS
; Size: 323 bytes. Origin: #x1002C3BD45
; D45:       8D41F1           LEA EAX, [RCX-15]               ; no-arg-parsing entry point
; D48:       A80F             TEST AL, 15
; D4A:       755F             JNE L2
; .....

To see the actual compiler-macro expansion, use a function inline-generic-function.

(let ((*features* (cons :inline-generic-function *features*)))
  (print (inline-generic-function '(plus a b))))

;; Inlining a generic function PLUS

(LET ((#:A1734 (1+ A)) (#:B1735 (1- B)))
  (EMATCH* (#:A1734 #:B1735)
    (((TYPE FLOAT) (TYPE FLOAT))
     (LET ((A #:A1734) (B #:B1735))
       (DECLARE (TYPE FLOAT A))
       (DECLARE (TYPE FLOAT B))
       (+ A B)
       (LET ((A #:A1734) (B #:B1735))
         (DECLARE (TYPE FLOAT A))
         (DECLARE (TYPE FLOAT B))
         (+ A B))))
    (((TYPE FIXNUM) (TYPE FIXNUM))
     (LET ((A #:A1734) (B #:B1735))
       (DECLARE (TYPE FIXNUM A))
       (DECLARE (TYPE FIXNUM B))
       (+ A B)
       (LET ((A #:A1734) (B #:B1735))
         (DECLARE (TYPE FIXNUM A))
         (DECLARE (TYPE FIXNUM B))
         (+ A B))))))

Since ematch from Trivia pattern matcher expands into thoroughly typed dispatching code, a sufficiently smart compiler would compile + into machine assembly, which is the case at least in SBCL.

Automatic compile-time dispatching

If the code is inlined in a typed environment, smart compilers like sbcl can detect certain branches are not reachable, thus removing the checks and reducing the code size. This is equivalent to compile-time dispatch. In the example below, the code for dispatching FLOAT is removed.

(defun func-using-inlined-plus-and-type-added (a b)
  " ; disassembly for FUNC-USING-INLINED-PLUS-AND-TYPE-ADDED
; Size: 29 bytes. Origin: #x10031E7788
; 88:       4801F9           ADD RCX, RDI                     ; no-arg-parsing entry point
; 8B:       488BD1           MOV RDX, RCX
; 8E:       48D1E2           SHL RDX, 1
; 91:       710C             JNO L0
; 93:       488BD1           MOV RDX, RCX
; 96:       41BB70060020     MOV R11D, 536872560              ; ALLOC-SIGNED-BIGNUM-IN-RDX
; 9C:       41FFD3           CALL R11
; 9F: L0:   488BE5           MOV RSP, RBP
; A2:       F8               CLC
; A3:       5D               POP RBP
; A4:       C3               RET
"
  (declare (inline plus))
  (declare (optimize (speed 3) (safety 0)))
  (declare (type fixnum a b))
  (plus a b))

If the types does not match, errors are signalled by EMATCH, which is consistent with the behavior of standard generic functions.

Enabling Inlining Globally

Inlining is not globally enabled by default. This is because the inlined code becomes obsoleted when the generic function definition changes, and therefore you generally do not want to make them inlined during the development.

It can be enabled globally by adding :inline-generic-function flag in *features*, which is useful when you build a standalone binary. When this feature is present, all inlinable generic functions are inlined unless it is declared notinline.

(push :inline-generic-function *features*)

Benchmark Setting

We tested two generic functions, one of which is a standard-generic-function, and another is an inlined-generic-function.

Both generic functions follow the definition below:

(defgeneric plus (a b)
  [(:generic-function-class inlined-generic-function)])
(defmethod plus :around ((a number) (b number))
  (+ a b)
  (call-next-method))
(defmethod plus ((a fixnum) (b fixnum))
  (+ a b))
(defmethod plus ((a double-float) (b double-float))
  (+ a b))

We tested them with and without inline declaration, i.e.,

(defun func-using-plus (a b)
  (declare (optimize (speed 3) (safety 0)))
  (plus a b))

(defun func-using-inlined-plus (a b)
  (declare (inline plus))
  (declare (optimize (speed 3) (safety 0)))
  (plus a b))

Thus, we have 4 configurations in total. The experiment is run under AMD Phenom II X6 processor 2.8GHz with SBCL 1.3.1 (launched by Roswell). The benchmark function is shown below:

(defvar *input* (iter (repeat 1000)
                     (collect (cons (random 100.0d0) (random 100.0d0)))
                     (collect (cons (+ 20 (random 100)) (+ 20 (random 100))))))
(defun benchmark ()
  (time (iter (for (a . b) in *input*)
              (func-using-normal-plus a b)))
  (time (iter (for (a . b) in *input*)
              (func-using-normal-inlined-plus a b)))
  (time (iter (for (a . b) in *input*)
              (func-using-plus a b)))
  (time (iter (for (a . b) in *input*)
              (func-using-inlined-plus a b))))

We first run the benchmark function 1000 times in order to calibrate the CPU cache. We then run the gc and invoke the benchmark function once more. We use the result of this final run in order to make sure the machine state is stabilized.

Result

Since the difference in the runtime is relatively small due to the small amount of computation, we consider the processor cycles only. We found that the cost of generic function invocation is considerably low when an inlined-generic-function is invoked with inline declaration.

metaclass and inline declarationprocessor cyclesconsing
standard-generic-function, not inlined742,2850
standard-generic-function, inlined726,0230
inlined-generic-function, not inlined7,865,080523,760
inlined-generic-function, inlined74,1200

Note that the third case, where the inlined-generic-function is not inlined, is slower than the normal generic function. This would be because we use the non-standard metaclass for representing the generic function and the normal optimization provided by the implementation is not performed. However, this is not a problem because we consider the third case only takes place during the development.

Conclusion

We showed that โ€ฆ well, anyway, this is not a paper. Enjoy!

Dependencies

This library is at least tested on implementation listed below:

  • SBCL 1.3.1 on X86-64 Linux 3.19.0-39-generic (authorโ€™s environment)

Also, it depends on the following libraries:

trivia by Masataro Asai
NON-optimized pattern matcher compatible with OPTIMA, with extensible optimizer interface and clean codebase
closer-mop by Pascal Costanza
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.
alexandria by
Alexandria is a collection of portable public domain utilities.
iterate by
Jonathan Amsterdamโ€™s iterator/gatherer/accumulator facility

Usage Note

When you use this library as part of your system, make sure that the method definitions are re-evaluated in load-time. This is necessary because the inlining information for the method could be lost after the compilation, i.e., the FASL file does not keep the defmethod form that should be inlined later.

The only thing you need is:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defmethod ...)
  ...)

Installation

Quicklisp available.

Author

Copyright

Copyright (c) 2015 Masataro Asai ([email protected])

License

Licensed under the LLGPL License.

More Repositories

1

trivia

Pattern Matcher Compatible with Optima
Common Lisp
334
star
2

aaai-template

latex template for various conferences, as well as wise-man's overleaf (overleaf is terrible!)
TeX
128
star
3

latplan

LatPlan : A domain-independent, image-based classical planner
Python
75
star
4

eazy-gnuplot

Super Duper Doopa Booka Lispy Gnuplot library
Common Lisp
59
star
5

sbcl-wiki

maybe-wrong sbcl internals
54
star
6

eazy-opencl

OpenCL binding for Common Lisp
Common Lisp
48
star
7

asdf-viz

ASDF system dependency visualizer
Common Lisp
37
star
8

lisp-namespace

no more discussion on lisp-1 vs lisp-2. THIS IS LISP-N.
Common Lisp
35
star
9

trivial-signal

UNIX signal handling library for Common Lisp.
Common Lisp
33
star
10

eazy-project

Boost your development!
Common Lisp
22
star
11

common-lisp-extensions

list of extensions beyond CL available in lisp implementations, and the status of its spread.
21
star
12

macroexpand-dammit

a portable code walker for Common Lisp by John Fremlin
Common Lisp
12
star
13

type-r

The complete collection of accessor functions and patterns to access the elements in a compound type specifier
Common Lisp
12
star
14

common-lisp-project-ideas

Discuss future project ideas
10
star
15

eazy-process

Yet Another Portable Library for Process Handling / Subshell Invokation
Common Lisp
9
star
16

recursive-macroexpansion

Provides another `macroexpand`
Common Lisp
8
star
17

bit-ops

Tools for writing optimized bit-vector routines
Common Lisp
8
star
18

eazy-documentation

One-shot solution to the CL library documentation generator.
Common Lisp
8
star
19

immutable-struct

Simple library that encourage the use of functional programming + pattern matching.
Common Lisp
7
star
20

ArriVAL

Yet Another Classical planning plan validator written in modern Common Lisp
Common Lisp
7
star
21

dynotune

Automated parameter tuner for CL
Common Lisp
6
star
22

hypercast

H Y P E R C A S T
Common Lisp
5
star
23

alien

Common Lisp
5
star
24

latplan-fosae

First Order State Auto Encoder implementation for Latplan
Python
5
star
25

quicklisp-project-submission

Submit to quicklisp-project/issues from your REPL!
Common Lisp
5
star
26

another-org-info

Yet Another Org-mode HTML Presentation Script
JavaScript
5
star
27

trainable-object

Provides an metaclass and APIs for the trainable funcallable instances. (WIP)
Common Lisp
5
star
28

cl-rrt

Common Lisp implementation of RRT (Rapidily exploring Random Tree), a fast probabilistic multidimentional path-plannning algorithm. Note: It will still work, but it is an old work. I think the implementation is not be very efficient because my lisp hacking has significantly improved since when I wrote this library.
Common Lisp
5
star
29

trivialib.type-unify

Unification library aimed specifically for the "polymorphic type specifiers" with type variables
Common Lisp
4
star
30

trivialib.red-black-tree

based on optima-red-black-tree
Common Lisp
4
star
31

cl-rlimit

Common lisp interface to unix rlimit -- ensure the performance of your program!
Common Lisp
4
star
32

serializable-object

An abstract class for serializable CLOS objects.
Common Lisp
4
star
33

file-local-variable

File-local variable independent from ASDF
Common Lisp
4
star
34

trivial-package-manager

A simple interface to distro-specific package managers.
Common Lisp
3
star
35

inner-conditional

Series of macros which optimizes the inner conditional jumps of looping, iterating, anything
Common Lisp
3
star
36

type-i

Type Inference Utility on unary type-checking predicates
Common Lisp
3
star
37

trivialib.bdd

BDD and ZDD
Common Lisp
3
star
38

simpath

Common Lisp
2
star
39

play-on-matrix

play with SBCL, matrix operation, VOP optimization
Common Lisp
2
star
40

dirtylogman

Command line tool for reading lots of log files
Common Lisp
2
star
41

data-structures-in-common-lisp

A survey of data structure availability / quality in common lisp
Common Lisp
2
star
42

remlic

Reimplementation of an interpretable machine learning system MLIC in Common Lisp
Common Lisp
2
star
43

dsama

Implementation of Double-Stage Action Model Acquisition
Common Lisp
2
star
44

dataloader

A universal file loader for various images/audio data formats in Common Lisp
Common Lisp
2
star
45

shibuya-posix

Provides a complete but minimum CFFI bindings to ALL posix standard header files.
Common Lisp
1
star
46

sas-parser

Fast Downward SAS parser
Common Lisp
1
star
47

structure-interface

Non-CLOS, compile-time, inlined, fast method dispatching system
Common Lisp
1
star
48

syntactic-optimization

adds context-aware optimization not available in SBCL
Common Lisp
1
star
49

ammunition

utility problem
Common Lisp
1
star
50

dd-schema

A higher-level helper library for CL-CUDD.
Common Lisp
1
star
51

goal-of-life

Goal-driven, optimal development of your life
Common Lisp
1
star
52

fast-downward-search

separate repository for Fast Downward search module
C++
1
star
53

cffi-documentation

Sample output of eazy-documentation for CFFI
HTML
1
star
54

check-throw-catch-usage

tools to download quicklisp data and investigate the source codes
Shell
1
star
55

planner-scripts

Wrapper for various planners, with resource limitations
Shell
1
star
56

aspectm

aspect-oriented hooks
Common Lisp
1
star
57

cl-pthread

Wrapper layer for POSIX pthread on common lisp.
Common Lisp
1
star