Last time we tried to test Class VS Struct, and I got several comments on ways I could improve the tests. We will be looking at performance improvements that can be made for defstruct. We will be running on an Intel Core i5 Pixelbook using SBCL (except where CCL is specified).
You can find the code:
https://github.com/Slids/lisp-perf-test/blob/master/class-v-struct-boa.lisp
Random Takes Time
Starting, random adds quite a bit of time. It used enough time to massively obfuscate the time things were taking. We can see how much time random is taking
(time (dotimes (i 1000000000) (random 100)))
Evaluation took:
31.172 seconds of real time
31.168020 seconds of total run time (31.168000 user, 0.000020 system)
99.99% CPU
50,123,948,880 processor cycles
0 bytes consed
Argument Constructors are Bad
The next problem is using the default struct constructor instead of using a a constructor with positional arguments. I say, why should this by slower? They say, it’s a bug. We see:
(defstruct (simple-struct (:constructor make-simple-struct (slot-1 slot-2 slot-3))) (slot-1) (slot-2) (slot-3)) (time (dotimes (i 1000000000) (make-simple-struct 1 2 3)))
Evaluation took:
9.602 seconds of real time
9.714198 seconds of total run time (9.268526 user, 0.445672 system)
[ Run times consist of 2.013 seconds GC time, and 7.702 seconds non-GC time. ]
101.17% CPU
15,438,933,331 processor cycles
32,000,006,848 bytes consed
(defstruct simple-struct-2 (slot-1) (slot-2) (slot-3)) (time (dotimes (i 1000000000) (make-simple-struct-2 :slot-1 1 :slot-2 2 :slot-3 3)))
Evaluation took:
20.735 seconds of real time
20.927129 seconds of total run time (20.470935 user, 0.456194 system)
[ Run times consist of 2.453 seconds GC time, and 18.475 seconds non-GC time. ]
100.93% CPU
33,340,558,882 processor cycles
32,000,007,568 bytes consed
So we get pretty large savings making a boa-constructor!
We can do more and declaim the constructor inline:
(declaim (inline make-simple-struct-3)) (defstruct (simple-struct-3 (:constructor make-simple-struct-3 (slot-1 slot-2 slot-3))) (slot-1) (slot-2) (slot-3)) (time (dotimes (i 1000000000) (make-simple-struct-3 1 2 3)))
Evaluation took:
0.363 seconds of real time
0.362868 seconds of total run time (0.362868 user, 0.000000 system)
100.00% CPU
583,539,405 processor cycles
0 bytes consed
We were able to stack-allocate the struct leading to quite the savings and no garbage collection. I wouldn’t suggest this very often but it’s an impressive change.
Rerunning Instantiation Tests
I will re-run the instantiation test with our defstruct having the boa-constructor and not using random.
Note: For CCL the struct instantiation test took: 0.238095 seconds. Why was class so terrible? CCL folks?
For there information the output was:
(LOOP FOR I FROM 1 TO RUN-TIMES DO (MAKE-INSTANCE ‘SIMPLE-CLASS :SLOT-1 I :SLOT-2 I :SLOT-3 I))
took 33,453,955 microseconds (33.453957 seconds) to run.
68,225 microseconds ( 0.068225 seconds, 0.20%) of which was spent in GC.
During that period, and with 4 available CPU cores,
33,421,986 microseconds (33.421986 seconds) were spent in user mode
66,531 microseconds ( 0.066531 seconds) were spent in system mode
800,000,528 bytes of memory allocated.
303 minor page faults, 0 major page faults, 0 swaps.
Thanks
I would like to thank @dougk (Google) and @stassats (Reddit account) for the comments and suggestions!
As usual I like to end with a picture: