Friday, April 3, 2020

Some questions of Block Compilation of Lisp

index

Preface

Several weeks ago, SBCL 2.0.2 updated with a "new" feature called Block Compilation. This is the first time I hear this term, I go collect some information of this new feature and I found this article.

Questions

After read this article I have several questions:

  • If I enable this optimization, do I lost the ability of redefine the function

  • What are the differences between this optimization and inline

  • How faster this optimization can achieve

Verification

Let me verify them one by one


If I enable this optimization, do I lost the ability of redefine the function

The answer is yes I can.

(defun foo (x y)
  (bar x y))

(defun bar (x y)
  (+ x y))

Then I compile whole file and load it in repl:

(compile-file "/Users/BC.lisp" :block-compile t :entry-points nil :output-file "/Users/BC.fasl")

(load "/Users/BC.fasl")

(foo 1 1) ;; => 2

Then I redefine bar function:

(defun bar () (print "a"))

(foo 1 1) ;; => 2
(bar) ;; => "a"

So I still have ability to redefine function bar, but even foo call bar, the foo's behavior hasn't been changed because bar be redefined.

How about I don't enable Block compilation?

(compile-file "/Users/BC.lisp" :block-compile nil :entry-points nil :output-file "/Users/BC.fasl") ;; turn off Block compilation

(load "/Users/BC.fasl")

(foo 1 1) ;; => 2

(defun bar () (print "a")) ;; redefine bar

(foo 1 1) ;; ERROR
; Evaluation aborted on #<SB-INT:SIMPLE-PROGRAM-ERROR "invalid number of arguments: ~S" {1002751533}>.

As we can see, if I turn off Block compilation, each time I call foo, foo is gonna to call bar inside. So if I redefine bar, foo's behavior has been changed by my redefinition.


What are the differences between this optimization and inline

I asked this question on SO, here is the post


How faster this optimization can achieve

I always use some stupid way to test productivity -- time function. So, I use it again:

;;; new file
(defun foo (x y z)
  (bar x y z))

(defun bar (x y z)
  (max x y z))

Same, compile and load it:

(compile-file "/Users/BC.lisp" :block-compile nil :entry-points nil :output-file "/Users/BC.fasl") ;; turn off Block compilation

(load "/Users/BC.fasl")

(time (loop repeat 3000000 do (foo 1 2 3))) ;; run 3 millions times

Then, I get the result:

Evaluation took:
  0.040 seconds of real time
  0.040597 seconds of total run time (0.040512 user, 0.000085 system)
  102.50% CPU
  117,978,902 processor cycles
  0 bytes consed

Next, do same thing but turn on Block compilation this time:

(compile-file "/Users/BC.lisp" :block-compile t :entry-points nil :output-file "/Users/BC.fasl")

(load "/Users/BC.fasl")

(time (loop repeat 3000000 do (foo 1 2 3)))

This time, the answer is

Evaluation took:
  0.037 seconds of real time
  0.037057 seconds of total run time (0.036985 user, 0.000072 system)
  100.00% CPU
  107,609,083 processor cycles
  0 bytes consed

Well, it just a little bit faster. I guess if my function is more complex, it should be much faster than just max function.