Subject: bug#73431: Add `setf` support for `stream.el` in ELPA

From: Philip Kaludercic <[email-masked]>
Date: Fri, 04 Oct 2024 08:55:20 +0000

Okamsn <[email-masked]> writes:

> Philip Kaludercic wrote:
>> Okamsn <[email-masked]> writes:
>>> Michael Heerdegen wrote:
>>>> Does changing the internal representation of streams have an effect
>>>> on the speed of the run code?
>>>
>>> I think that it does make it slower. I am trying to test it, and I think
>>> that making records is slower than making cons cells. I think that
>>> accessing the rest of the stream takes longer because the accessors
>>> created by `cl-defstruct` always perform type checking. It seems to take
>>> about twice as long when compared to naively using `car` and `cdr`.
>>>
>>> Do you think that it would be better to disable the type checking in the
>>> accessors? If so, would you please share how to do that? The manual
>>> talks about using `(optimize (safety 0))` in a declare form, but it also
>>> seems to say that it cannot be done locally for just the `cl-defstruct`
>>> usage. If it cannot be done, do think it makes sense to use
>>> `make-record` directly, along with custom function to replace the
>>> generated accessors?
>> 
>> It the overhead noticeable, or just measurable?
>
> I’m not sure what counts as “noticeable”.

I'd say that real-world code that uses stream.el gets slower.  For me
the main value of synthetic benchmarks is only the speed difference in
orders of magnitude and in the number of GC interrupts.

> Using the benchmark macros given at 
> https://github.com/alphapapa/emacs-package-dev-handbook#benchmarking, I 
> tested getting the "first" and “rest” of streams, both as fresh streams 
> and as already evaluated streams.
>
> These are the results I get for a stream from a list using the current 
> implementation:
>
> | Form                     | Tot. runtime | # of GCs | Tot. GC runtime |
> |--------------------------+--------------+----------+-----------------|
> | stream 10 evald: rest    |     0.015259 |        0 |               0 |
> | stream 10: rest          |     0.044525 |        0 |               0 |
> | stream 10 evald: first   |     0.059650 |        0 |               0 |
> | stream 10: first         |     0.074379 |        0 |               0 |
> | stream 100: rest         |     0.132317 |        0 |               0 |
> | stream 100 evald: rest   |     0.132821 |        0 |               0 |
> | stream 100 evald: first  |     0.198041 |        0 |               0 |
> | stream 100: first        |     0.205684 |        0 |               0 |
> | stream 1000 evald: rest  |     1.249168 |        0 |               0 |
> | stream 1000: rest        |     1.250730 |        0 |               0 |
> | stream 1000 evald: first |     1.835921 |        0 |               0 |
> | stream 1000: first       |     1.857300 |        0 |               0 |
>
> These are the results I get for a stream from a list using the 
> struct-based implementation:
>
> | Form                     | Tot. runtime | # of GCs | Tot. GC runtime |
> |--------------------------+--------------+----------+-----------------|
> | stream 10 evald: rest    |     0.036241 |        0 |               0 |
> | stream 10 evald: first   |     0.048213 |        0 |               0 |
> | stream 10: rest          |     0.048221 |        0 |               0 |
> | stream 10: first         |     0.048285 |        0 |               0 |
> | stream 100 evald: rest   |     0.312544 |        0 |               0 |
> | stream 100: rest         |     0.321046 |        0 |               0 |
> | stream 100 evald: first  |     0.439694 |        0 |               0 |
> | stream 100: first        |     0.441674 |        0 |               0 |
> | stream 1000: rest        |     3.032329 |        0 |               0 |
> | stream 1000 evald: rest  |     3.142683 |        0 |               0 |
> | stream 1000: first       |     4.113174 |        0 |               0 |
> | stream 1000 evald: first |     4.132561 |        0 |               0 |
>
> You can see that the struct-based run times are about twice as long.  I 
> think this is from the extra work done by the accessors.  For example, 
> the type-checking is run multiple times when accessing the “first” and 
> “rest” slots, because the accessors are also used in `stream--force`.

Type checking isn't always that bad; Do you see an (easy) way to avoid
type checking from running multiple times?

-- 
	Philip Kaludercic on siskin