[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: A question about a scheme function with two input notes

From: Jean Abou Samra
Subject: Re: A question about a scheme function with two input notes
Date: Fri, 30 Dec 2022 23:53:37 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.6.0

[This is the continuation of]

Le 30/12/2022 à 15:07, David Kastrup a écrit :
You conflate "parsing" and "reading".  For #{...#}, there is a
rudimentary scan for # and $

Yes, I know that.

that tends to deliver false positives (which then just don't get evaluated 
later on)

This sounds worrisome. Do you have examples of that happening?

The Scheme reader will throw an error that is incomprehensible
for the user in case this happens and # or $ is followed by something
that is not a valid Scheme expression, so avoiding false positives
is important for correctness.

and may get confused into
overlooking actual positives.  \ offers a lot more potential for getting
this wrong.
It would also make Guile evaluate one (lambda () <variable>) per use
of \ in #{ #}, which I don't believe is costly.
It is.  The optimisation of not putting up closures for most constants
made a relevant performance difference.

I did a test with a file constructed using

$ echo '\version "2.25.1"' >
$ for i in `seq 10000`; do echo '##{ #1 #}' >>; done

I pinned my CPUs at 2GHz (to reduce statistical noise) and measured:

$ hyperfine -m 30 'master/build/out/bin/lilypond'
Benchmark 1: master/build/out/bin/lilypond
  Time (mean ± σ):      1.446 s ±  0.021 s    [User: 1.395 s, System: 0.107 s]
  Range (min … max):    1.410 s …  1.498 s    30 runs

$ hyperfine -m 30 'build/out/bin/lilypond'
Benchmark 1: build/out/bin/lilypond
  Time (mean ± σ):      1.609 s ±  0.083 s    [User: 1.551 s, System: 0.108 s]
  Range (min … max):    1.539 s …  1.917 s    30 runs

The second one is with the removal of the (or (symbol? expr) ...) test
in parser-ly-from-scheme.scm. The difference is about 0.16s for 10,000
expressions, or 16μs / expression.

Next, I tried this:

$ cat
\version "2.25.1"

#(define-syntax-rule (repeat n body body* ...)
   (let ((n* n))
     (do ((i 0 (1+ i)))
       ((eqv? i n*))
       body body* ...)))

#(repeat 10000
  #{ #1 #})


$ hyperfine -m 30 -i 'master/build/out/bin/lilypond'
Benchmark 1: master/build/out/bin/lilypond
  Time (mean ± σ):     840.7 ms ±   7.9 ms    [User: 793.3 ms, System: 96.9 ms]
  Range (min … max):   825.1 ms … 859.3 ms    30 runs

$ hyperfine -m 30 -i 'build/out/bin/lilypond'
Benchmark 1: build/out/bin/lilypond
  Time (mean ± σ):     822.7 ms ±  13.0 ms    [User: 779.4 ms, System: 98.8 ms]
  Range (min … max):   803.9 ms … 855.5 ms    30 runs

The main difference is that this one executes the reader logic
only once, but evaluates the resulting #{ #} expression many
times (which is closer to what happens with music functions).
This one actually turns out faster without the optimization.
It could be due to the fact that a closure makes
bail out before looking up 'compile-scheme-code in the Scheme
option hash table, or to scm_primitive_eval (constant) being
a little slower than ly_call (lambda_returning_constant).

At any rate, the difference is ~2μs / evaluation.

And there is absolutely no associated gain at all since
$identifier will work just fine where scoping is needed.  The gain of
"people using Scheme but not understanding it might by chance escape
trivial errors" is not really a gamechanger.

The Notation manual has a section on what it calls "substitution
functions", which is separate from the section on music functions
in the Extending manual, and I think that is for good reason.
Those "substitution functions" have the form of a define-music-function
where the body is just a #{ ... #}. There is little Scheme "programming"
involved for that apart from cargo-cult copy&pasting of the
define-music-function template and picking type predicates
from the list in the documentation.

I regularly see people (especially on lilypond-user-fr lately)
wondering why returning #{ \book { ... } #} from a music function
does not work, and that sort of thing.

It's LilyPond's kind of "macro" facility, which is accessible to
all users including those who know nothing about Scheme, and
I think there is a point in making it behave more like users
would expect ("just use variables like you always do, except
that the variables change every time").


Attachment: OpenPGP_signature
Description: OpenPGP digital signature

reply via email to

[Prev in Thread] Current Thread [Next in Thread]