forked from acl2/acl2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
memoize-raw.lisp
5137 lines (4269 loc) · 210 KB
/
memoize-raw.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
; ACL2 Version 8.1 -- A Computational Logic for Applicative Common Lisp
; Copyright (C) 2018, Regents of the University of Texas
; This version of ACL2 is a descendent of ACL2 Version 1.9, Copyright
; (C) 1997 Computational Logic, Inc. See the documentation topic NOTE-2-0.
; This program is free software; you can redistribute it and/or modify
; it under the terms of the LICENSE file distributed with ACL2.
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; LICENSE for more details.
; Written by: Matt Kaufmann and J Strother Moore
; email: Kaufmann@cs.utexas.edu and Moore@cs.utexas.edu
; Department of Computer Science
; University of Texas at Austin
; Austin, TX 78712 U.S.A.
; memoize-raw.lisp -- Raw lisp definitions for memoization functions, only to
; be included in the hons-enabled ACL2 executables (which is all ACL2
; executables starting with Version_7.2).
; The original version of this file was contributed by Bob Boyer and Warren
; A. Hunt, Jr. The design of this system of Hash CONS, function memoization,
; and fast association lists (applicative hash tables) was initially
; implemented by Boyer and Hunt. Contributions have been made since then by
; Jared Davis, Matt Kaufmann, J Moore, and Sol Swords.
(in-package "ACL2")
; SECTION: To do, near-term
; - Incorporate some Lisp comments into the :doc. In particular, probably the
; *record-xxx* and *report-xxx* should be incorporated into the :doc. In
; general, add/improve documentation, including for the following.
; - *memoize-summary-order-list*
; - *sort-to-from-by-calls*
; - memoize (e.g., clarify/emphasize that :recursive actually avoids inlining
; and :recursive nil avoids counting recursive calls in (memsum)); maybe
; explain how fns like LEN with special raw-Lisp code can thus be memoized
; only with :recursive nil; perhaps move some things from the Lisp comment
; to the :doc
; - Memoize-summary and memsum. Incorporate long comment in memoize-summary.
; Update :doc (maybe other topics too) to clarify that we stop at
; most-positive-mfixnum [see safe-incf]. Alternatively, memsum could just
; print a message if we ever hit that limit, and require an optional
; argument if one wants the results anyhow.
; - Update :doc for the pons/hons-wash soundness fix checked into git,
; especially for hons-wash.
; - Consider counting pons calls even when a number is returned; else make sure
; the :doc is clear about this. (See the definition of pons.)
; - Perhaps write an essay summarizing how memoization works, including
; discussion of *memoize-call-array* (or at least pointing to the comment
; there).
; - Add interface functions, perhaps including the following. I can imagine a
; single macro or function that takes, say, a keyword argument in order to
; print (to the comment window, or to state perhaps using the oracle) what's
; desired. OR -- maybe suitable utilities can go into books, if we make
; enough interface functions available.
; o *Memoize-summary-order-list* (as a state global perhaps)
; o Profile, unmemoize-profiled; maybe even profile-all and profile-acl2, but
; maybe keep those instead in books/centaur/memoize/old/profile*, or maybe
; both, with a sort of autoloading as is done for WET.
; o And so on -- see in particular function with this comment: "Note: This
; function should probably either be made available in the loop or else
; eliminated."
; - If we want to be able to memoize n functions, then it looks like the number
; of (virtual) columns of *memoize-call-array* need only be something like
; n+5. So perhaps that array need not be a square array -- it can have fewer
; columns than rows -- in which case we can save close to a factor of two on
; space. But that might well require considerable code re-work; for example,
; memoize-call-array-grow computes (/ *2max-memoize-fns* 2), but surely that
; would have to change. It's not clear that it's worth saving a factor of
; roughly two on something whose default is of size 10^6 and might not need
; to grow.
; - We have calls like (make-symbol "BODY-NAME") and bindings like
; (,*mf-old-caller* *caller*) -- the former interns a new symbol each time
; (though with the same, EQ result each time) while the latter saves a symbol
; using a defconstant form in file acl2.lisp. Consider making this all more
; consistent in style.
; SECTION: To do, longer-term
; - Consider eliminating the function memoized-values (and memstat) or, perhaps
; better yet, have it return a data structure rather than printing individual
; values.
; - Replace (COND (T <code>)) by <code> in what is generated by
; memoize-fn-def. (For an example, look just below memoize-fn-def.)
; - Perhaps think about this comment in the definition of pons:
; It might be tempting to think about creating a static cons here.
; - Consider using memo-max-sizes-entry records not only to guess good sizes,
; but also to guess good rehash sizes.
; - Tests such as (< (* 100 local-tt) tt) and (> (* 10000 local-tt) tt) in
; function memoize-summary-after-compute-calls-and-times restrict reporting
; to cases where the time is at least 1% of the time taken by the main
; function. We might want to make this customizable.
; - Consider finer-grained way to make memoization thread-safe.
; - Consider eliminating defun-one-output in most cases, simply for the sake of
; elegance.
; - Consider eliminating some uses of our-syntax, instead relying on normal
; ACL2 printing.
; - Consider printing sufficiently small floats without E notation.
; - Think about when to use defv vs. defg; maybe pick one and stick with it?
; Or perhaps better yet, this question might be moot after considering the
; next item.
; - Perhaps bundle up user-settable variables (see "User-settable variables for
; recording" below) into a single defrec stored in a state global, and
; provide suitable interface functions. Careful though -- some books may
; bind current such variables, so we'd probably want to provide a convenient
; mechanism for doing such bindings. As Jared notes, if such records are
; stored in ordinary special variables -- i.e., not introduced with defg or
; defv -- then although some performance advantage of defg/defv might be
; lost, it can be easier to support multi-threading by taking advantage of
; the fact that bindings of specials are thread-local. He also reminds us of
; the ability to use local variables [see for example mf-shorten and the use
; of physical-memory-cached-answer in the defun of physical-memory], and
; tells us that these can give similar efficiency to defv/defg with the
; similar drawback of not being thread-local, but with the advantage that the
; locality can ease not only ACL2 source code maintenance, but also avoid
; problems for other people's code (because it can't access such globals
; directly).
; - Document user-settable variables (or if the item just above is implemented
; with a record stored in a state global, document the fields and/or their
; readers and setters).
; - Try turning egc on in CCL. (See function acl2h-init.) Jared says (9/2014)
; that "leaving on EGC could really reduce the memory requirements of 95+% of
; the ACL2 jobs in the community books, and could be really good."
; - Consider moving set-gc-strategy (note that set-gc-strategy-builtin-delay
; was formerly called start-sol-gc) and supporting functions out of the ACL2
; code base, quite possibly into community books directory
; books/centaur/misc/memory-mgmt/. But if so, perhaps retain some way (an
; interface of some sort) to get the effect of set-gc-strategy when
; desirable, without the need for trust tags. Perhaps consider the effect on
; ACL2(hp). Jared thinks that removing memory management responsibility out
; of the ACL2 sources "might be especially useful if you are going to turn
; EGC back on by default."
; - In pons-addr-of-argument, (+ hl-dynamic-base-addr (hl-staticp x)) is
; probably a fixnum. Especially for GCL, maybe wrap (the fixnum ...) around
; it. Also maybe declaim a return type of (values (or null fixnum)), in case
; that helps GCL in particular; or maybe make this a macro (or at least
; inline it, but then maybe we still want the declaim form).
; - Revisit the comment in mf-index about how we use hl-staticp without
; checking that the argument is as normally expected.
; - Consider this suggestion from Jared, quoting a comment in memoize:
; Jared Davis suggests that we consider bundling up these 13 parameters, for
; example into an alist. He says: "Various subsets of these arguments occur in
; spaghetti fashion throughout the code for memoize, add-trip, the
; memoize-table stuff, etc."
; - Investigate odd behavior of with-lower-overhead for LispWorks, as explained
; in a comment there.
; SECTION: Preliminaries
;; One may comment out the following PUSHNEW and rebuild to get profiling
;; times not based upon the curious and wondrous RDTSC instruction of some x86
;; processors. On a machine with several cores, the RDTSC values returned by
;; different cores may be slightly different, which could lead one into such
;; nonsense as instructions apparently executing in negative time, when timing
;; starts on one core and finishes on another. To some extent we ignore such
;; RDTSC nonsense, but we still can report mysterious results since we have no
;; clue about which core we are running on in CCL (or, presumably, SBCL).
#+(or ccl sbcl)
(eval-when
(:execute :compile-toplevel :load-toplevel)
(when #+ccl (fboundp 'ccl::rdtsc)
#+sbcl
; In SBCL 1.4.7, function sb-impl::read-cycle-counter is no longer defined, as
; it was in older versions (at least as recently as 1.4.2). With (apropos
; "READ-CYCLE-COUNTER") we found sb-vm::%read-cycle-counter; actually, Keshav
; Kini first pointed us to sb-vm::%read-cycle-counter. Oddly, fboundp returns
; nil on this symbol, and it cannot be called as the top-level function symbol
; in the read-eval-print loop. But it seems to work fine here, and it seems to
; be equivalent to sb-impl::read-cycle-counter in older versions. We completed
; a successful build and used it for (profile 'rewrite) followed by
; :mini-proveall and then (memsum), on top of both SBCL 1.1.11 and SBCL 1.4.7.
(not (ignore-errors (sb-vm::%read-cycle-counter)))
(pushnew :RDTSC *features*)))
#+rdtsc
(defconstant *2^30-1-for-rdtsc*
(1- (ash 1 30)))
#+rdtsc
(defmacro rdtsc ()
; rdtsc always returns a fixnum, by zeroing out the top two bits of the real
; rdtsc value. We add with the possibly slightly faster logior.
#+ccl
'(ccl::rdtsc)
#+sbcl
'(multiple-value-bind
(t1 t2)
(sb-vm::%read-cycle-counter)
(declare (fixnum t1 t2))
(the fixnum (logior (the fixnum (ash (logand t1 *2^30-1-for-rdtsc*)
32))
t2))))
#+rdtsc
(defmacro rdtsc64 ()
; Rdtsc64 may return a bignum, but it returns all 64 bits of the real rdtsc
; value.
#+ccl
'(ccl::rdtsc64)
#+sbcl
'(multiple-value-bind
(t1 t2)
(sb-vm::%read-cycle-counter)
(declare (fixnum t1 t2))
(+ (ash t1 32) t2)))
; SECTION: Multithreaded Memoization
; This section was initially contributed by Jared Davis, who contributed the
; following comment (indented and re-filled here).
; The implementation is in many ways suboptimal because, for the most part, I
; just want to get something working. To keep things simple, I have added a
; single, global memoize lock. This lock needs to be acquired whenever a
; thread needs to do something that could mutate soundness-related memoize
; state. This is straightforward, but could (obviously) lead to a lot of
; lock contention if you write multithreaded code that is using memoization
; heavily. It could also slow down single threaded code because of the
; overhead of acquiring and releasing locks all the time.
;
; In the future it would probably be worth reworking things so that, e.g.,
; each memo table has its own separate lock. On the other hand, that makes
; things like deadlock a lot trickier. That sounds pretty horrifying, so I'm
; going to stick with the simple but possibly slow implementation for now.
(defg *enable-multithreaded-memoization*
;; By default set up multithreaded memoization only if we are running on
;; ACL2(p). However, nothing here is really ACL2(p) specific, and users of
;; plain ACL2(h) can change this in raw Lisp if they know what they're doing.
;;
;; Note for raw Lisp hackers. We ordinarily consult this variable
;; dynamically. However, to make memoized functions execute more
;; efficiently, we only consult its value AT MEMOIZE TIME when creating the
;; memoized definitions. Consequences of this:
;;
;; - If you ever want to change this, e.g., because you are using ordinary
;; ACL2(h) instead of ACL2(hp), but you want to do something with raw
;; Lisp threads, then you really need to do something like this:
;;
;; (setq *enable-multithreaded-memoization* t)
;; (rememoize-all t)
;;
;; - You need to be careful to do the rememoize-all AFTER you have already
;; loaded any books that might contain memoized functions. For instance,
;; if you do the above and then include a book with a memoized F, then
;; the definition of F will not do appropriate locking until you again do
;; a rememoize-all.
#+acl2-par t
#-acl2-par nil)
; Historical Note: Many function names below are prefixed with ``mf-''. This
; was a convention followed by Bob and Warren in their original prototype of
; this code, and preserved (and possibly followed) by Jared. We have continued
; to follow the convention but we confess we don't know why ``mf-'' was used or
; what it stood for! Our best guess is ``memoize function.'' The prefix
; ``ma-'' stood for ``memoize call array.''
(defun mf-multiprocessing (flg &optional quiet)
(cond ((iff *enable-multithreaded-memoization* flg)
(when (not quiet)
(format *standard-output*
"~%; Note: Multiprocessing is already ~a.~%"
(if *enable-multithreaded-memoization* "on" "off"))))
(t
(unwind-protect ; make sure we finish!
(setq *enable-multithreaded-memoization* flg)
(rememoize-all t))))
flg)
(defg *global-memoize-lock*
(make-lock "*GLOBAL-MEMOIZE-LOCK*"))
(defmacro with-global-memoize-lock (&rest forms)
; We consult *enable-multithreaded-memoization* at runtime; compare with macro
; with-global-memoize-lock-static.
#-(or ccl sb-thread lispworks) ; with-lock-raw is just progn
`(progn ,@forms)
#+(or ccl sb-thread lispworks)
; We tried the following alternative to the code below, in order to avoid
; duplicating code during macroexpansion. But for ACL2(h) (in its normal mode
; -- that is without *enable-multithreaded-memoization*, hence not ACL2(hp))
; this seemed to slow down the regression slightly. This macro is only used in
; this file so we're not too concerned about the compiled code duplication.
; (let ((body (gensym)))
; ;; Avoid duplication of ,forms to avoid code blowup
; `(flet ((,body () ,@forms))
; (if *enable-multithreaded-memoization*
; (with-lock-raw *global-memoize-lock* (,body))
; (,body))))
`(if *enable-multithreaded-memoization*
(with-lock-raw *global-memoize-lock* ,@forms)
(progn ,@forms)))
(defmacro with-global-memoize-lock-static (&rest forms)
;; Faster but less safe way to grab the lock, i.e., this version consults
;; *enable-multithreaded-memoization* at macroexpansion time instead of at
;; run time. This is nothing more than a progn in the single-threaded case.
;;
;; NOTE: ONLY USE THIS WHEN CREATING MEMOIZED DEFINITIONS. For everything
;; else, the safer version should be plenty fast.
#-(or ccl sb-thread lispworks) ; with-lock-raw is just progn
`(progn ,@forms)
#+(or ccl sb-thread lispworks)
(if *enable-multithreaded-memoization*
`(with-lock-raw *global-memoize-lock* . ,forms)
`(progn . ,forms)))
(defmacro mf-mht (&rest args)
; Call this macro with :shared :default unless we know that the call
; (mf-multiprocessing t) will blow away the hash table (and reconstitute it as
; a :shared table).
; We believe that at least one of :lock-free or :shared must be t in order to
; avoid "not owner of hash table" errors, and the following experiments support
; that belief.
; (defparameter *ht* (make-hash-table :lock-free nil :shared t))
; ; ok:
; (ccl:process-run-function "t1" (lambda () (setf (gethash 'my-key *ht*) 17)))
; (defparameter *ht* (make-hash-table :lock-free t :shared nil))
; ; ok:
; (ccl:process-run-function "t2" (lambda () (setf (gethash 'my-key *ht*) 17)))
; (defparameter *ht* (make-hash-table :lock-free nil :shared nil))
; ; error:
; (ccl:process-run-function "t3" (lambda () (setf (gethash 'my-key *ht*) 17)))
; The following experiment suggests that we get better performance with :shared
; t than with :lock-free t. In fact :shared t may rival performance with
; :shared nil.
; cd books/centaur/tutorial/
; (include-book "alu16-book")
; (time$ (def-gl-thm foo
; :hyp (and (alu16-test-vector-autohyps)
; (equal op *op-mult*)
; (< a (expt 2 12))
; (< b (expt 2 12)))
; :concl (equal (alu16-basic-result)
; (mod (* a b) (expt 2 24)))
; :g-bindings (alu16-default-bindings)))
; :lock-free nil :shared nil
; 48.42 seconds realtime, 48.29 seconds runtime
; (4,169,078,496 bytes allocated).
; :lock-free t :shared nil
; 54.33 seconds realtime, 54.19 seconds runtime
; (4,528,524,384 bytes allocated).
; :lock-free nil :shared t
; 48.35 seconds realtime, 48.23 seconds runtime
; (4,169,087,440 bytes allocated).
; To access or update tables created by mf-mht, we use mf-gethash and
; mf-sethash. Of course, :shared t is not sufficient to avoid the use of locks
; when performing atomic operations. Moreover, as of this writing only CCL,
; among the supported host Lisps, pays attention to :shared (or :lock-free).
; Pons tables contain flex-alists, which can contain hash-tables. Since
; hons-raw.lisp is loaded before memoize-raw.lisp, we define our own interface
; with hl-flex-acons; see mf-flex-acons. See "SECTION: Ponsing: Creating keys
; ("ponses") for memoization tables".
(if (assoc-keyword :shared args) ; then use :shared as supplied
`(hl-mht ,@args)
`(if *enable-multithreaded-memoization*
(hl-mht :shared :default ,@args)
(hl-mht ,@args))))
(defmacro mf-gethash (key ht)
; Ht was created by mf-mht. See mf-mht.
`(gethash ,key (the hash-table ,ht)))
(defmacro mf-sethash (key val ht)
; Ht was created by mf-mht. See mf-mht.
`(setf (gethash ,key (the hash-table ,ht)) ,val))
(defmacro mf-flex-acons (elem al)
`(hl-flex-acons ,elem ,al *enable-multithreaded-memoization*))
(defmacro mf-hash-table-count (ht)
; Ht was created by mf-mht. See mf-mht.
`(hash-table-count (the hash-table ,ht)))
(defmacro mf-hash-table-size (ht)
; Ht was created by mf-mht. See mf-mht.
`(hash-table-size (the hash-table ,ht)))
(defmacro mf-maphash (lam ht)
; Ht was created by mf-mht. See mf-mht.
`(maphash ,lam (the hash-table ,ht)))
; SECTION: Constants and globals
; This section contains most of the constants and globals in this file,
; including all globals that might be modified (for example, directly by the
; user, or during initialization).
; NOTE: It might be best to skip this section on a first reading, or at most
; browse it lightly, returning to look at specific constants and globals when
; they are encountered later.
; Subsection: User-settable variables for recording
; To minimize metering overhead costs, one may set the "*RECORD-" variables
; below to NIL before memoizing.
; *RECORD-BYTES* and other *RECORD-...* variables are bound in
; REMEMOIZE-ALL, so we use DEFPARAMETER rather than DEFG.
; Warning: Keep the following set of globals in sync with memoize-info, as
; described there.
(defparameter *record-bytes*
; If *record-bytes* is not NIL at the time a function is memoized, we attempt
; to count all heap bytes allocated during calls of that function.
; It is quite possibly fine to replace the following form by T. However, we
; leave this restriction, which produces nil in 32-bit CCL, because it has
; probably been here for many versions through 6.5 and at this point (August
; 2014) we don't expect much use of 32-bit CCL when doing memoization.
(and (or (member :ccl *features*) ; most Lisps don't report bytes
(member :sbcl *features*))
(> most-positive-fixnum (expt 2 32))))
(defparameter *record-calls*
; If *RECORD-CALLS* is not NIL at the time a function is memoized, we count all
; calls of the function.
t)
(defparameter *record-hits*
; If *RECORD-HITS* is not NIL at the time a function is memoized, we count the
; number of times that a previously computed answer is used again.
t)
(defparameter *record-mht-calls*
; If *RECORD-MHT-CALLS* is not NIL at the time a function is memoized, we
; record the number of times that a memoize hash-table is created for the
; function. See *report-mht-calls* below for an explanation of why we
; might create many hash-tables for a function.
t)
(defparameter *record-pons-calls*
; If *RECORD-PONS-CALLS* is not NIL at the time a function is memoized, pons
; calls are counted.
t)
(defparameter *record-time*
; If *RECORD-TIME* is not NIL the time a function is memoized, we record the
; elapsed time for each outermost call of the function.
t)
; Subsection: User-settable variables for reporting
(defg *mf-print-right-margin* ; rather arbitrary default; seems to work well
79)
(defv *report-bytes*
; When memoize-summary is invoked, print the number of bytes allocated on the
; heap, if this variable is not nil at that time and the host Lisp supports
; providing that information.
#+(or ccl sbcl) t #-(or ccl sbcl) nil)
(defv *report-calls*
; When memoize-summary is invoked, print the number of calls, if this variable
; is not nil at that time.
t)
(defv *report-calls-from*
; When memoize-summary is invoked, print the following if this variable is not
; nil at that time: which functions called the given function, how many times,
; and how long the calls took.
t)
(defv *report-calls-to*
; When memoize-summary is invoked, print the following if this variable is not
; nil at that time: which functions were called by the given function, how many
; times, and how long the calls took.
t)
(defv *report-hits*
; When memoize-summary is invoked, print the number of times that a previously
; computed answer was reused, if this variable is not nil at that time.
t)
(defv *report-mht-calls*
; When memoize-summary is invoked, print the number of times that a memo
; hash-table for the function was created, if this variable is not nil at that
; time. This may be of interest to those who memoize functions with stobj
; arguments, since when such a stobj changes, the function's memoization hash
; table is deleted, and then later, when necessary, created afresh.
t)
(defv *report-pons-calls*
; When memoize-summary is invoked, print the number of calls of pons, if this
; variable is not nil at that time.
t)
(defv *report-time*
; When memoize-summary is invoked, print information about the total time used
; to compute the outermost calls, if this variable is not nil at that time.
t)
(defv *report-on-memo-tables*
; When memoize-summary is invoked, print information about memo tables, if this
; variable is not nil at that time.
t)
(defv *report-on-pons-tables*
; When memoize-summary is invoked, print information about pons tables, if this
; variable is not nil at that time.
t)
(defg *sort-to-from-by-calls* nil)
(defg *memoize-summary-order-reversed*
; When memoize-summary is invoked, report on functions in order from least to
; greatest, if this variable is not nil at that time.
nil)
(defg *mf-print-alist-width* 45)
(defg *memoize-summary-order-list*
; This is a list of functions that MEMOIZE-SUMMARY uses to sort all functions
; that are currently memoized in preparation for displaying information about
; them. The order used is lexicographical, with the first order having the
; most weight. Each order function takes one argument, a symbol, and returns a
; rational.
; The default is '(total-time number-of-calls).
; Options for the functions include the following.
; bytes-allocated [CCL and SBCL only]
; bytes-allocated/call [CCL and SBCL only]
; execution-order
; hits/calls
; pons-calls
; number-of-calls
; number-of-hits
; number-of-memoized-entries
; number-of-mht-calls
; symbol-name-order
; time-for-non-hits/call
; time/call
; total-time
'(total-time number-of-calls))
(defg *memoize-summary-limit*
; The value is either nil or the maximum number of functions upon which
; memoize-summary reports. A nil value means report on all."
100)
(defg *condition-nil-as-hit*
; Set this variable to a non-nil value if you want the failure of a memoization
; condition to be recorded as a "hit". That seems counterintuitive, which is
; why the default is nil. (Note profiling doesn't report misses, so this
; decision doesn't impact profiling reports.) But if you care a lot about the
; "Time per missed call" reported by memsum, maybe you'll want to set this to
; t. NOTE that this value is consulted at the time a function is memoized, not
; at call time or memsum time.
nil)
; Subsection: Miscellaneous user-settable variables
(defvar *never-memoize-ht*
; Function exit-boot-strap-mode further populates this hash table by calling
; function initialize-never-memoize-ht.
; Users can set this with never-memoize.
; Note: Consider locking uses of this global variable.
(let ((ht (mf-mht :test 'eq :shared :default)))
(loop for x in *stobjs-out-invalid*
do (mf-sethash x t ht))
#+rdtsc (mf-sethash #+ccl 'ccl::rdtsc
#+sbcl 'sb-impl::read-cycle-counter
t
ht) ; used in memoize implementation
ht))
; Subsection: Variables not user-settable (managed by the implementation)
(defg *float-ticks/second* (float (expt 2 31))) ; 2 GHz
(defg *float-ticks/second-initialized* nil)
(declaim (float *float-ticks/second*))
(defg *pons-call-counter* 0)
(defg *pons-misses-counter* 0)
(declaim (type mfixnum *pons-call-counter*))
(declaim (type mfixnum *pons-misses-counter*))
(defg *mf-shorten-ht*
; This global is used in mf-shorten. As explained there, no lock is needed.
; Since hons-copy is applied to form the keys, we can use eql as the test.
(mf-mht :test 'eql :shared :default))
(defun initial-memoize-info-ht ()
(if *enable-multithreaded-memoization*
(mf-mht :shared :default)
; This table is essentially blown away and reconstituted by rememoize-all,
; hence by (mf-multiprocessing t). So we avoid using :shared :default below,
; in order to make the table a bit more efficient in the uniprocessing case --
; though probably efficiency isn't particularly important for this hash table.
(mf-mht)))
(defg *memoize-info-ht*
; This hash table associates memoized function symbols with various
; information. It has two mappings in one (see setf forms in memoize-fn), that
; is, two kinds of keys: symbols and numbers.
;
; 1. It maps each currently memoized function symbol, fn, to a DEFREC record
; of type MEMO-INFO-HT-ENTRY. One field of that record is :NUM.
;
; 2. It maps each such :NUM value back to the corresponding symbol, fn.
(initial-memoize-info-ht))
(declaim (hash-table *memoize-info-ht*))
(defparameter *memo-max-sizes*
; This hash table binds function names to memo-max-sizes-entry structures.
; Jared originally added this table because he wanted to know how big
; memoization tables were getting (so that he could set up appropriate initial
; sizes), but when tables are cleared they are thrown away, so for tables that
; are frequently cleared it wasn't possible to see how large the table had
; become.
; After seeing the information, it seemed a good idea to use it to infer what a
; good table size might be when the memo table is re-created.
(mf-mht :shared :default))
(defun initial-memoize-call-array (n)
; Warning: It is assumed in sync-memoize-call-array that this array has a
; length of 1 when n is 1.
(make-array n :element-type 'mfixnum :initial-element 0))
(defg *memoize-call-array*
(initial-memoize-call-array 1))
; Essay on the *Memoize-Call-Array*
; *Memoize-call-array*, `ma' for short, is a 1D array that is used to store
; information about how many times one memoized function calls another and how
; much time is spent in such calls. It also holds performance data on the
; callers themselves (independent of which subroutines they're calling).
; This array is conceptually two-dimensional. To read from or write to this
; array, see ma-index and the ma-index-xxx macros. Each "dimension" of ma is
; twice the maximum number of memoized functions; so the length is 4 times the
; square of the maximum number of memoized functions. This is a very
; inefficient scheme; roughly half of the allocated space is unused! See
; below.
; The length of this array is both a fixnum and an mfixnum; see
; memoize-call-array-grow.
; Ma is initialized in memoize-init. Think of ma as a two dimensional array
; with dimensions (twice the max number of memoized functions) x (twice the max
; number of memoized functions).
; COLUMNS. Each 'column' corresponds to info about a memoized function, but
; the first five columns are 'special'. We count rows and columns starting at
; 0. Column 0 is used as scratch space by compute-calls-and-times for sums
; across all functions. Columns 1, 2, and 3 are not currently used at all.
; Column 4 is for the anonymous 'outside caller'. Column 5 is for the first
; memoized function, column 6 is for the second, and so on.
; ROWS. In columns 5 and greater, row 0 is used to count 'bytes', row 1 counts
; 'hits', row 2 counts mht calls, and row 4 counts pons calls that don't simply
; return a number (i.e., an address in the static-hons case). (3 was formerly
; used to count hons calls, but that is no longer the case.) The elements of
; an ma column starting at row 10 (twice the number assigned to the first
; memoized function) are for counting and timing info. Suppose column 7
; corresponds to the memoized function FOO and column 12 corresponds to the
; memoized function BAR. Whenever FOO calls BAR, element 2*12 of column 7 will
; be incremented by 1, and the total elapsed ticks for the call will be added
; to element 2*12+1 of column 7. See the picture below.
; Though ma may 'grow', it may not grow while any memoized function is running,
; and here is why: every memoized function has a cached opinion about the size
; of ma. (Here is a technical explanation, which one may wish to ignore. For
; example, memoize-fn binds the variable fn-col-base to (ma-index fnn), which
; is computed based on the size of ma at the time the function is memoized.
; Fn-col-base is passed to memoize-fn-outer-body, where it is used to generated
; code for the memoized function.)
; Here is an ASCII picture of the *memoize-call-array* with a key to explain
; what is stored where.
; <--------------max---------------><--------------max------------>
; 0 1 2 3 4 5 i ^
; |
; 0 [ ][x][x][x][ ][ ] ... [b] ... [x][x][x][x] ... [x][x][x][x] |
; 1 [ ][x][x][x][ ][ ] ... [h] ... [x][x][x][x] ... [x][x][x][x] |
; 2 [ ][x][x][x][ ][ ] ... [m] ... [x][x][x][x] ... [x][x][x][x] |
; 3 [ ][x][x][x][ ][ ] ... [x] ... [x][x][x][x] ... [x][x][x][x] |
; 4 [ ][x][x][x][ ][ ] ... [p] ...
; 5 [ ][x][x][x][ ][ ] ... [ ] ... ... ... max
; ... ... ...
; 2j [s][x][x][x][ ][ ] ... [c] ... [x][x][x][x] ... [x][x][x][x] |
; 2j+1 [s][x][x][x][ ][ ] ... [t] ... [x][x][x][x] ... [x][x][x][x] |
; ... ... ... |
; [ ][x][x][x][ ][ ] ... ... [x][x][x][x] ... [x][x][x][x] |
; [ ][x][x][x][ ][ ] ... ... [x][x][x][x] ... [x][x][x][x] v
; ... ... ... [x][x][x][x] ... [x][x][x][x] ^
; ... ... ... [x][x][x][x] ... [x][x][x][x] |
; ... ... ... ... ... max
; ... ... ... [x][x][x][x] ... [x][x][x][x] |
; [ ][x][x][x][ ][ ] ... ... [x][x][x][x] ... [x][x][x][x] |
; [ ][x][x][x][ ][ ] ... ... [x][x][x][x] ... [x][x][x][x] v
; Dimensions: (2 * max number of memoized fns)^2
; Contents:
; s -- scratch space for compute-calls-and-times as it sums across all functions
; (for calls in even-numbered rows and ticks in odd-numbered rows)
; b -- number of bytes used in calls of fni
; h -- number of hits on calls of fni
; m -- number of mht calls on fni
; x -- unused
; p -- number of pons calls for fni
; c -- number of times fni called fnj
; t -- number of ticks spent when fni called fnj
; Each memoized function symbol is assigned a number. The first ``real''
; function number is 5. Because rows 0-4 of every function's column are
; devoted to performance data about that function, and due to a poorly realized
; desire to make a function's number serve both as its column number and its
; row number, we end up wasting columns 0-4 in every row because we use rows
; 0-4 in every column (!). We don't entirely waste them: column 0 is used for
; scratch space and column 4 is used for the generic outside caller, but we
; have not found a use for columns 1, 2, or 3, or for column 0 of the odd rows.
; The uniformity of treating a function's number both as its column and row
; number is ``poorly realized'' because two rows are allocated to every
; function, e.g., data for function j resides in row 2j and row 2j+1, so the
; actual row number is not the function's number after all!
; Clearly this whole scheme -- evolved over several steps from Boyer and Hunt's
; original prototype -- is very wasteful but we are reluctant to change it
; simply because it works and we have not apparently suffered for lack of space
; for ma.
; The functions ma-index-calls and ma-index-ticks return index in the actual 1D
; array containing the call count and tick count, respectively, of the
; corresponding call from function i to function j.
; The following function, which is not part of our code, illustrates how to map
; from a pair of symbols, parent and child (caller and callee), to the index of
; the *memoize-call-array* containing the number of times parent called child.
; It calls symbol-to-fixnum, which maps a memoized function symbol to its
; natural number counterpart.
; (defun call-count (parent child)
; ; Return the number of times parent has called child, as per
; ; *memoize-call-array*.
; (let* ((parent-index (symbol-to-fixnum parent))
; (child-index (symbol-to-fixnum child))
; (calls-index (ma-index-calls child-index parent-index))
; (calls (aref *memoize-call-array* calls-index)))
; (values calls
; :parent-index parent-index
; :child-index child-index
; :calls-index calls-index)))
; For example,
; (defun f1 (x) (declare (xargs :guard t)) (cons x x))
; (defun f2 (x) (declare (xargs :guard t)) (cons (f1 x) (f1 x)))
; (memoize 'f1)
; (memoize 'f2)
; (f2 7)
; (f2 8)
; (f2 9)
; :q
; ; These return T:
; (equal (call-count 'f2 'f1) 6)
; (equal (call-count 'f1 'f2) 0)
; ; This gives more complete information, namely call-count,
; ; parent index, child index, index of call count:
; (call-count 'f2 'f1)
; The assignment of numbers to function symbols is handled by
; *memoize-info-ht*. Use (maphash (function (lambda (fn val) (print (list fn
; val)))) *memoize-info-ht*) to inspect.
; As of this writing (Version_7.0), the number 4 is assigned to the
; non-function "outside-caller" (denoting the caller of outermost call of a
; memoized function) and 5-7 are assigned to the first three built-in functions
; listed below; they are always memoized by explicit calls to memoize in
; boot-strap-pass-2-b.lisp. Of course, this set and the particular numbers may
; change in future releases, but we include this information here to indicate
; aspects of the basic scheme. BAD-LISP-OBJECTP may not always be memoized,
; i.e., it is not in ACL2(hp). See *thread-unsafe-builtin-memoizations* for a
; list of functions that are not memoized in ACL2(hp).
; fn number assigned
; "outside-caller" 4
; FCHECKSUM-OBJ 5
; PKG-NAMES-MEMOIZE 6
; WORSE-THAN-BUILTIN 7
; BAD-LISP-OBJECTP 8
; By the way, the assignment of a unique number to each memoized function is
; done initially by symbol-to-fixnum-create.
(declaim (type (simple-array mfixnum (*))
*memoize-call-array*))
(defg *callers-array*
; At the end of a call of compute-calls-and-times, which is called by
; memoize-summary, (aref *callers-array* n) will contain the numbers of the
; functions that have called the function numbered n.
(make-array 0))
(declaim (type (simple-array t (*)) *callers-array*))
(defconstant *ma-bytes-index* 0)
(defconstant *ma-hits-index* 1)
(defconstant *ma-mht-index* 2)
; Originally there was an *ma-hons-index* of 3, but we no longer count honses.
(defconstant *ma-pons-index* 4)
; We associate memoized function symbols with fixnums, and vice versa. See
; symbol-to-fixnum-create.
(declaim (type (and mfixnum
; Normally we are happy to declare variables in this file to be of mfixnum type
; only, but here we also declare these of type fixnum so that indices i below
; such variables may safely appear in forms (loop for i fixnum ...).
fixnum)
*max-symbol-to-fixnum*
*initial-2max-memoize-fns*
; *initial-max-symbol-to-fixnum* ; defconstant, below
*2max-memoize-fns*))
(defconstant *initial-max-symbol-to-fixnum* 4)
(defg *max-symbol-to-fixnum* *initial-max-symbol-to-fixnum*)
(defv *initial-2max-memoize-fns* 1000)
(defg *2max-memoize-fns* ; length of *memoize-call-array*
*initial-2max-memoize-fns*)
(defg *memoize-init-done* nil)
(defg *hons-gentemp-counter* 0)
(defg *memoize-use-attachment-warning-p* t)
(defv *number-of-arguments-and-values-ht*
; Mf-note-arity is an interface for updating this variable.
; This hash table maps a symbol fn to a cons pair (a . d), where a is the
; number of inputs and d is the number of outputs of fn. NIL for a or d
; indicates 'don't know'.
(mf-mht :shared :default))
(declaim (hash-table *number-of-arguments-and-values-ht*))
; Globals not included in this section:
; - *caller*
; - *10-days*
; - *float-internal-time-units-per-second*
; SECTION: Our-syntax
(defmacro our-syntax (&rest args)
; This macro extends Common Lisp's with-standard-io-syntax by binding *package*
; and *readtable*. It may not be necessary to bind *readtable* here, but it
; has been done historically and it seems harmless. We bind *package* because
; the Common Lisp utility with-standard-io-syntax binds *package* to the
; CL-USER package.
; The settings in our-syntax are oriented towards reliable, standard, vanilla,
; mechanical reading and printing, and less towards human readability.
; Please, before changing the following, consider existing uses of this macro
; insofar as the changes might impact reliable, standard, vanilla, mechanical
; printing.
; Consider using our-syntax-nice.