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
Add BigArray.Genarray.free #389
base: trunk
Are you sure you want to change the base?
Conversation
Free the memory used by a bigarray, setting all dimensions to zero to prevent further use. This is useful to ensure that memory-mapped files are closed promptly, without waiting for the garbage collector. Otherwise, mapping a large number of files may hit operating system limits on the number of open files. If e.g. [sub_left] is used to create multiple bigarrays backed by the same memory block, [free] must be called on all of them before the memory is actually released. Test program: (* ocamlc unix.cma bigarray.cma test.ml -o test && ./test *) open Bigarray let maps = ref [] let open_and_close () = let fd = Unix.(openfile "/etc/passwd" [O_RDONLY; O_NONBLOCK] 0o644) in let mapped = Array1.map_file fd char c_layout false 100 in (* Make bug more obvious by ensuring no GC *) maps := mapped :: !maps; (* Array1.free mapped; *) Unix.close fd let () = let i = ref 0 in while true do incr i; open_and_close (); Printf.printf "Mapped %d files\n%!" !i done ... Mapped 35589 files Mapped 35590 files Fatal error: exception Unix.Unix_error(Unix.ENFILE, "open", "/etc/passwd")
@xavierleroy implemented such a proposal some time ago and then reverted it. One problem with changing dynamically the dimensions is that it prevents interesting optimizations such as lifting/eliminating array bound checks (e.g. in the body of a for-loop iterating over indices of a bigarray). Or one can still assume that dimension are non mutable, at the risk of getting segfaults/bus error instead of proper out-of-bounds exceptions if the bigarray is accessed after being freed. |
What's the current recommended solution then? I hit this twice recently. First in ocaml-git, which uses mmap to read files (and runs out, similar to the example posted above). Second, in a Mirage unikernel where I mapped a share memory region for a network device and started an Lwt thread watching it. When the device was disconnected, the memory was unmapped. Later, other data was mapped to the same address, and the old thread started trying to process it (which didn't end well). |
Declare an new type that ensure that an access never occur after freeing. module Unloadable : sig
type t
val map_file : string -> t
val free : t -> unit
val get : t -> int -> char
end = struct
type t = { mutable freed : bool; data : (...) Bigarray.Array1.t }
let map_file file = { freed = false; data = Bigarray.Array1.map_file ... }
external unsafe_free_ba : (...) Bigarray.Array1.t -> unit = "..."
let free t = t.freed <- true; unsafe_free_ba t.data
let get t x =
if t.freed then raise ...
else Bigarray.Array1.get t.data x
end If the indirection cost really matters you can imagine adding the 'freed' field to the C representation and add new primitives to check it when doing an access and implement this new module using it. For the ocaml-git case, you also probably want to have a read-only type to allow sharing without doing the weak hash table dance, but that's another story. |
The problem is that we expose the fact that we're using a Bigarray. For example, there is code that does:
If I hide the fact that t.buffer is a bigarray, I can't use it with Lwt (and I can't ensure that Lwt will check whether the Cstruct is freed without changing Bigarray itself). |
On 04/01/2016 16:15, Thomas Leonard wrote:
|
For reference, I considered the following thought experiment: we could store in each bigarray an initialization-defined immutable bit that indicates whether or not its size may change in the future. The bigarray disposable/release/free function would fail dynamically on size-immutable arrays (ok, a dynamic check is not so nice), and bound-checking hoisting would only be allowed assuming this bit checked size-immutable (which is worth it as soon as at least two size-checks can be factorized; but admittedly such a there-is-potential-for-hoisting-logic may be harder to implement). (You may think of phantom types for statical classification of may-be-resized, but the pain of transitioning end-user APIs could be a bit too large.) The potential performance benefits of never-resizing bigarrays were discussed in MPR#5203. It may be the case that with the more aggressive inlining settings of flambda, a non-escaping analysis suffices to locally optimize bigarray accesses without imposing global non-resizability guarantees. It is, unfortunately, at least one release too soon to be able to know this. |
Just for reference, I think that it is since PR #115 that you can no longer play tricks with bigarray sizes (I used to do the same kind of thing for temporarily mapping GPU memory pointers). |
This was about lifting the access to the data pointer, which assumes that this pointer does not change. This property is not strictly related to resizability (which would affect the ability to lift bound checks).
I guess you are not proposing to compile the code twice and dispatch dynamically between the two versions according to the value of the flag; but rather to somehow let the programmer assert explicitly that the flag is set (causing a runtime failure if not, and allowing later optimizations). Is that right? Something like: let foo a =
Bigarray.assert_immutable a;
(* from this point, the compiler nows that 'a' has an immutable size and/or data-pointer *)
... |
For reference, Jane Street's
https://ocaml.janestreet.com/ocaml-core/109.24.01/doc/core/Bigstring.html#6_Destruction |
I think that the best solution would be for the compiler to assume that there are no calls to |
For what it's worth, I also think that the current policy has caused too much troubles for too little actual gain. We'll see how effectively flambda's inlining can help here, but I would be in favor of allowing explicit deallocation of bigarrays. |
@gasche I agree, especially since CSE and inlining should keep the performance penalty to a minimum. |
@gasche writes: "I would be in favor of allowing explicit deallocation of bigarrays." And turn off CSE on bigarray bounds and data pointer, degrading performance? Or keeping CSE and risk segfaults instead of "out of bound" exceptions when accessing a bigarray that has been freed? This is the dilemma... |
Yes -- I feel that the qualitative difference in some interesting system workflows being made impossible is worse than the quantitative performance regressions that are being discussed. Supposedly we have better inlining capacities than we had previously, and in any case a local (post-inlining) escape analysis should be enough to restore the optimization in most cases. I don't know who has bigarray-intensive computations in production ( @alainfrisch ? ) but they could probably test this assumption. Another route to take would be to declare |
We definitely have bigarray-intensive computations in production. I think allowing bigarray to be freed without implementing this escape analysis would be pretty bad for us |
I'm not sure I understand what's wrong with the @xavierleroy Can you tell me where this reasoning is flawed? |
@xavierleroy Ping, please. |
Nearly three years later, I have no idea what to do with this PR. I implemented bigarray deallocation a long time ago, then was convinced by @alainfrisch to take it out. Now I don't care and will let you guys fight it out. |
…locking_section Avoid holding domain_lock when using backup thread
The host doesn't treat the Wayland connection as closed until the ring is unmapped, which previously only happened when the garbage collector got to it. This resulted in windows not disappearing immediately when the application exited. This is a hack: see ocaml/ocaml#389 This also improves error handling around connection shutdown: - Check the device is still open before accessing the shared ring (since it might now be unmapped). - Silently ignore sends after a close. ocaml-wayland may need to flush its transmit buffer and this isn't an error. - Return end-of-file immediately when reading if the device has been closed. Cstruct.shift checks that the buffer is still valid, which can give a poor error message if we close during a read.
The host doesn't treat the Wayland connection as closed until the ring is unmapped, which previously only happened when the garbage collector got to it. This resulted in windows not disappearing immediately when the application exited. This is a hack: see ocaml/ocaml#389 This also improves error handling around connection shutdown: - Check the device is still open before accessing the shared ring (since it might now be unmapped). - Silently ignore sends after a close. ocaml-wayland may need to flush its transmit buffer and this isn't an error. - Return end-of-file immediately when reading if the device has been closed. Cstruct.shift checks that the buffer is still valid, which can give a poor error message if we close during a read.
@talex5: since the main uses for this seem to involve memory mapped buffers, another thought came to mind: what about replacing the underlying buffer with a |
That would certainly be more secure. However, it seems that people in this thread are not happy about segfaults at all, even deterministic ones (#389 (comment)):
|
@talex5: what about catching the segfaults and turning them into an exception? |
Being able to convert a synchronous signal into an exception would be nice and open new opportunities in various scenarios, not just for freeing the backing memory of the bigarray. See the discussion at #5471 for (essentially) a previous attempt. It is unfortunate that the discussion is not fully available publicly though. |
We've tried to do this for the last 25 years to convert "segmentation fault" signals to Stack_overflow exceptions, and it's a complete mess of machine and OS dependencies. One of the best things about OCaml 5 is that we got rid of that code. I wish it will never come back. |
Free the memory used by a bigarray, setting all dimensions to zero to prevent further use.
This is useful to ensure that memory-mapped files are closed promptly, without waiting for the garbage collector. Otherwise, mapping a large number of files may hit operating system limits on the number of open files.
It's also needed in MirageOS, where some Xen protocols require shared memory to be unmapped at certain points.
If e.g.
sub_left
is used to create multiple bigarrays backed by the same memory block,free
must be called on all of them before the memory is actually released.Test program:
For me, on Linux, this prints: