Thank you all for these quick and helpful responses! I've noticed the `call-with-output-string` mechanism while browsing various Racket code bases, but was under the impression that this is "just" a convenient abstraction over strings. Based on Bogdan's code and Robby's explanation, it's now clear that this thinking was wrong.
I also like Jens' code for its pure functional elegance. I'm surprised that building up a long list of short strings before joining them is so much faster. After all, isn't the final `string-join` essentially doing what the original code snipped did? (Clearly not! I will have a look at the implementation.) So, it seems that my intuitions about the costs of various operations were completely off here. Thank you for pointing this out. Tracking GC overheads by setting `PLTSTDERR` is very helpful in this regard. Off topic: I've enjoyed your "Racket hacking" screencasts, Bogdan! It's helpful to see how hacking on real world Racket code bases can look like. Best wishes, Alessandro On 27.06.21 16:29, Robby Findler wrote: > Replacing ` (~r x #:precision 1)` with `(number->string x)` and ditto > for `y` eliminates the overhead of contracts and brings about another 4x > speedup on my machine. > > That may not work, tho, depending on Alessandro's original usecase, > since the strings are different. > > I was also going to, as Bogdan does, recommend ports. In other > languages, strings are a sophisticated data structure, but in Racket > they are just linear arrays. The ports abstraction is the best place to > situate streaming stuff (especially if you're going to write the result > to a file in the end). String and byte ports are great for unit testing > too -- you can just drop in a file port in the same place for the real > code, minimizing the amount of code you need that's only for testing. > Robby > > > On Sun, Jun 27, 2021 at 9:10 AM Bogdan Popa <bog...@defn.io > <mailto:bog...@defn.io>> wrote: > > Hi Alessandro, > > Here is a version of your program that is about 30 times faster on my > machine (9s -> 300ms): > > #lang racket/base > > (require racket/flonum > racket/format > racket/port) > > (define (xy-vectors->string x-vec y-vec) > (call-with-output-string > (lambda (out) > (for ([i (in-naturals)] > [x (in-flvector x-vec)] > [y (in-flvector y-vec)]) > (unless (zero? i) > (write-char #\space out)) > (write-string (~r x #:precision 1) out) > (write-char #\, out) > (write-string (~r y #:precision 1) out))))) > > (time > (let ([x (make-flvector 100000)] > [y (make-flvector 100000)]) > (xy-vectors->string x y))) > > All the calls to `string-append` in your original program end up > allocating larger and larger strings and then immediately discarding > them on subsequent iterations, which is costly over many iterations. > > Hope that helps, > Bogdan > > Alessandro Motta writes: > > > Hi racket-users! > > > > I've recently become interested in Lisp/Scheme and have started to > hack > > in Racket. The excellent documentation, the fast integrated > search, and > > DrRacket have made that a real pleasure. > > > > Thank you for that! > > > > I've been working on a tool to convert notes from the reMarkable 2 > > tablet to SVG files. At the core is the conversion of (x, y) > coordinate > > pairs from two `flvector`s to a string of the form "x1,y1 x2,y2 > x3,y3". > > > > ``` > > (define (xy->string x y) > > (string-append > > (~r x #:precision 1) "," > > (~r y #:precision 1))) > > > > (define (xy-vectors->string x-vec y-vec) > > (for/fold ((coordinates "") > > (separator "") > > #:result coordinates) > > ((x (in-flvector x-vec)) > > (y (in-flvector y-vec))) > > (values (string-append > > coordinates > > separator > > (xy->string x y)) > > " "))) > > ``` > > > > This is currently the bottleneck for large conversion jobs. > > > > Profiling these functions with `profile-flame-graph` resulted in > > > > https://gist.githubusercontent.com/amotta/cfe4b19e24455af219521c9e94455c67/raw/dbbc87bd2f6dd4e27c33831749baa90fffdaed55/flvector-to-coordinates-string-flamegraph.svg > > <https://gist.githubusercontent.com/amotta/cfe4b19e24455af219521c9e94455c67/raw/dbbc87bd2f6dd4e27c33831749baa90fffdaed55/flvector-to-coordinates-string-flamegraph.svg> > > > > The full profiling script is available at > > https://gist.github.com/amotta/e76197082bb1bf63538ede01872917f3 > <https://gist.github.com/amotta/e76197082bb1bf63538ede01872917f3> > > > > Roughly 90% of time is spent in > `contract/private/arrow-val-first.rkt`. > > Based on my very limited understanding of Racket, it seems that > ~38% of > > time is spent handling keyword arguments (presumably `#:precision > 1`?). > > The `catnp` function (the conversion from flonum to string, I think) > > takes up only ~11% of time. > > > > Is this interpretation of the flame graph correct? If so, are > there any > > obvious blunders on my part? Any ideas for how to speed up this code? > > > > > > Best wishes, > > Alessandro > > -- > You received this message because you are subscribed to the Google > Groups "Racket Users" group. > To unsubscribe from this group and stop receiving emails from it, > send an email to racket-users+unsubscr...@googlegroups.com > <mailto:racket-users%2bunsubscr...@googlegroups.com>. > To view this discussion on the web visit > https://groups.google.com/d/msgid/racket-users/m2v95zcs9v.fsf%40defn.io > <https://groups.google.com/d/msgid/racket-users/m2v95zcs9v.fsf%40defn.io>. > -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/b308c924-0ef3-65a6-953d-a0e6db1ba3eb%40gmail.com.