-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SRFI 231: array-copy is not call/cc-safe #904
Comments
It makes sense for iterators and higher-order functions to be call/cc safe, because they are generic and should compose with continuations. This is not the case for (In either case the |
There was a lot of discussion (scores of emails) in July--September 2022 on the SRFI 231 mail list about these things, and Marc Nieper-Wißkirchen argued strongly, and convinced me to change a lot of code, to make the default procedures be "call/cc safe". I suppose the current discussion could move to the SRFI 231 mail list. Edit: The sample implementation converts generalized array arguments to |
Continuations can implicitly appear in the getter if, say, the programmer uses a construct like the What "call/cc" actually means is that (using continuations) one cannot detect mutation of previously allocated locations. Thus the suffix "!" seems correct to me. |
Thanks for the definition. It's important to be precise in specifying new concepts. We should be a little more precise - what we want to detect is mutations made by the procedure itself to allocations it made itself, and not by any of its arguments or the continuations involved. R7RS R7RS What's so special about arrays that they require this extra protection not available in the rest of the language? |
The definitions of Now saying that just because they are not forced to mutate doesn't mean that they are not allowed to would be the same as saying that just because (If This was already discussed outside of the context of SRFI 231 with Marc Feeley who also gave an efficient implementation of |
I'm curious as to what this efficient implementation is. The implementation in SRFI 133 is not call/cc safe: https://github.com/scheme-requests-for-implementation/srfi-133/blob/master/vectors/vectors-impl.scm#L823 |
Marc Feeley gives the following implementation of (define (vector-map-1 proc input-vect)
(define (vmap-1 proc input-vect i)
(if (< i (vector-length input-vect))
(let* ((result (proc (vector-ref input-vect i)))
(output-vect (vmap-1 proc input-vect (+ i 1))))
(vector-set! output-vect i result)
output-vect)
(make-vector i)))
(vmap-1 proc input-vect 0)) There is no GC pressure because the stack is used to build the list of intermediate values. It is a very clever solution in my opinion. All credit goes to Marc; he was the one who called attention to this issue. Fixing the implementations and making the specifications of SRFI 133 and some other SRFIs is on the TODO list for the large language. Efficiency (or the lack thereof) is not really an argument, in my opinion; first of all, an optimizing implementation will have to inline |
This probably performs well in benchmarks for small vectors, but you risk blowing the stack. Building an intermediate list is safer, but I realized we can just copy the vector at the end so the mutated locations are never seen:
Now if this is inlined and proc is trivial (like fx+ a constant) it's only twice as slow as the non-call/cc safe version instead of pathologically slow. Efficiency (both in time and space) is not everything but it matters. That's why we have TCO. My primary use case for SRFI 231 is neural networks. If we have something as simple as:
where a and b are large f8 arrays, the space usage is at least 16x as much for the current SRFI 231 reference implementation, and twice as much using my approach. Being limited to half your available memory is not good. |
I compared the speed of the current sample implementation of the SRFI and also a multi-dimensional analogue of Feeley's method for
I previously thought about this implementation and concluded one can discover that mutation occurred by capturing two continuations, for both
Gambit doesn't support any flonum operations other than IEEE double (although it supports "native" f32vectors). Gambit doesn't keep flonums in registers across basic block boundaries, so
Then you need another byte for the actual storage in the So I see this SRFI as useful for prototyping, algorithm design, and for actually slicing and dicing arrays in production code. Production code operations on large arrays may be best done using an FFI. If this brief note doesn't address your comments, I apologize. |
Sorry, I meant So To be clear, I'm not arguing against providing the call/cc safe versions, I just think they are an unusual novelty and should not be the default for arrays, but I should probably take that discussion to the SRFI list. |
Here's an experiment with
That's a 50% premium in CPU time. I don't know what else to say. |
I'm mostly talking about space, not time. If I want to use SRFI 231 like I use Pandas, I simply can't use |
To be clear, if I'm using f8 or f16 it's because I have a lot of data, and am generally talking about 100s of millions of elements, not 10,000, and I'd use billions if I had more memory. |
The example used 100,000,000 elements. Here's an example using
So 25 times the storage allocated, and a 50% time cost. |
Oh, sorry, yes I guess I can handle 100 million elements on my machine, but the argument is the same, it's just a question of where the limit is. On a more limited machine, or at billions or tens of billions |
The best implementation strategy for procedures like A fast and memory-efficient implementation strategy that should work for all implementations is to set a dynamic flag when a procedure like (I would like to hear your suggestions about an API that would make such things possible in portable code.) Until this is implemented, what is the problem of using |
After doing more experiments, looking at space and time usage, and thinking about the different kind of applications an array SRFI might support, I'm more comfortable with I'd be OK with people processing arrays so large that call/cc-safe procedures will require memory too large to fit in their machines needing to look up the available options of the non-call/cc-safe procedures to run their applications. Just my opinion. |
Here's a comparison between the two proposed implementations of
|
For each continuation captured, note the elements that have already been generated with this continuation. When this continuation is later reinstated, these elements must not be generated anew and must appear in the result of (This is what the prose of the definition of The "Feeley" version of |
vector-map is so rough in the related array SRFIs.I would rather think that it is the operation between array and array, like the matrix plus, multiplication.and then discuss about how call/cc is applied for the safety reason. |
Just as a record, I include here test code for interactions of continuations and the various "continuation-safe" array constructors. These pass on Gambit's implementation and fail on Chibi's. I understand that it's not a priority to change Chibi's implementation to make these pass, but I want to put the tests here all the same.
|
I believe
array-copy
is implemented asarray-copy!
, re-entering a getter's continuation affects results that have already been returned.Example:
Chibi returns
The sample implementation added to Gambit returns
The text was updated successfully, but these errors were encountered: