Skip to content

Commit 9fbd1a2

Browse files
committed
Add: Diffing two lists
1 parent 0ae4ac8 commit 9fbd1a2

File tree

2 files changed

+977
-750
lines changed

2 files changed

+977
-750
lines changed

README.org

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,75 @@ As is usually the case, the =cl-loop= macro expands to the most efficient code,
351351

352352
However, in some cases =cl-loop= may expand to code which uses =nconc=, which, as the benchmark shows, is much slower. In that case, you may write the loop without =cl-loop= to avoid using =nconc=.
353353

354-
**** Filtering a list
354+
**** Diffing two lists :lists:
355+
356+
As expected, =seq-difference= is the slowest, because it's a generic function that dispatches based on the types of its arguments, which is relatively slow in Emacs. And it's not surprising that =cl-nset-difference= is generally slightly faster than =cl-set-difference=, since it's destructive.
357+
358+
However, it is surprising how much faster =-difference= is than =cl-nset-difference=.
359+
360+
It's also nonintuitive that =-difference= suffers a large performance penalty by binding =-compare-fn= (the equivalent of the =:test= argument to =cl-set-difference=): while one might expect that setting it to =string== would give a slight performance increase, it's actually faster to let =-difference= use its default, =equal=.
361+
362+
Note that since this benchmark compares lists of strings, =cl-nset-difference= requires setting the =:test= argument, since it uses =eql= by default, which does not work for comparing strings.
363+
364+
#+BEGIN_SRC elisp :exports both :eval no-export
365+
(defmacro test/set-lists ()
366+
`(setf list1 (cl-loop for i from 0 below 1000
367+
collect (number-to-string i))
368+
list2 (cl-loop for i from 500 below 1500
369+
collect (number-to-string i))))
370+
371+
(let (list1 list2)
372+
(bench-multi-lexical :times 10 :ensure-equal t
373+
:forms (("-difference"
374+
(progn
375+
(test/set-lists)
376+
(-difference list1 list2)))
377+
("-difference string="
378+
(progn
379+
;; This is much slower because of the way `-contains?'
380+
;; works when `-compare-fn' is non-nil.
381+
(test/set-lists)
382+
(let ((-compare-fn #'string=))
383+
(-difference list1 list2))))
384+
("cl-set-difference equal"
385+
(progn
386+
(test/set-lists)
387+
(cl-set-difference list1 list2 :test #'equal)))
388+
("cl-set-difference string="
389+
(progn
390+
(test/set-lists)
391+
(cl-set-difference list1 list2 :test #'string=)))
392+
("cl-nset-difference equal"
393+
(progn
394+
(test/set-lists)
395+
(cl-nset-difference list1 list2 :test #'equal)))
396+
("cl-nset-difference string="
397+
(progn
398+
(test/set-lists)
399+
(cl-nset-difference list1 list2 :test #'string=)))
400+
("seq-difference"
401+
(progn
402+
(test/set-lists)
403+
(seq-difference list1 list2)))
404+
("seq-difference string="
405+
(progn
406+
(test/set-lists)
407+
(seq-difference list1 list2 #'string=))))))
408+
#+END_SRC
409+
410+
#+RESULTS:
411+
| Form | x faster than next | Total runtime | # of GCs | Total GC runtime |
412+
|----------------------------+--------------------+---------------+----------+------------------|
413+
| -difference | 7.16 | 0.084484 | 0 | 0 |
414+
| cl-nset-difference equal | 1.05 | 0.605193 | 0 | 0 |
415+
| cl-set-difference string= | 1.01 | 0.636973 | 0 | 0 |
416+
| cl-set-difference equal | 1.01 | 0.644919 | 0 | 0 |
417+
| cl-nset-difference string= | 1.19 | 0.650708 | 0 | 0 |
418+
| -difference string= | 1.59 | 0.773919 | 0 | 0 |
419+
| seq-difference | 1.05 | 1.232616 | 0 | 0 |
420+
| seq-difference string= | slowest | 1.293030 | 0 | 0 |
421+
422+
**** Filtering a list :lists:
355423

356424
Using ~-select~ from =dash.el= seems to be the fastest way:
357425

0 commit comments

Comments
 (0)