Skip to content
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

tweak GC in OCaml bindings #5600

Merged
merged 6 commits into from
Oct 14, 2021

Conversation

c-cube
Copy link
Contributor

@c-cube c-cube commented Oct 14, 2021

at Imandra we've had issues with recent versions of OCaml, where Z3 objects wouldn't be collected and would accumulate in RAM, eventually taking multiple GBs. There's some related issues for .NET I believe. This patch uses a different version of custom blocks (OCaml heap values that wrap foreign objects, such as a Z3 solver, expr, or context) with more explicit resource limits. I've hand picked limits that seem reasonable but it's subject to discussion. Pinging @ivg if he's interested.

this forces the GC to collect garbage when a few _large_ objects
(solver, etc.) are dead. The current code would let arbitrarily many
such objects die and not trigger a GC (which would have to come from
OCaml code instead)
@c-cube
Copy link
Contributor Author

c-cube commented Oct 14, 2021

This actually requires OCaml >= 4.08 to benefit from the new allocation function.

@wintersteiger
Copy link
Contributor

Conceptually this seems like a good idea, but I'd add some way for applications to tweak those numbers without making changes to the API code. I'm also slightly concerned that this breaks compatibility with older versions of OCaml, which may be a problem for some existing projects.

Minor: Why the change to Ubuntu 20? Does this hint at the fact that it won't work on Ubuntu 18 anymore? Also, I don't know what the guarantees of GC.finalise are, but in other systems (like .NET) this does not mean that an object is actually finalised at the request time (or indeed at all).

@c-cube
Copy link
Contributor Author

c-cube commented Oct 14, 2021

@wintersteiger the Ubuntu 20.04 is just because it seems to use the system OCaml, which is 4.05 on Ubuntu 18.04 if I read the logs correctly.

I agree it's unfortunate to require a more recent OCaml, but at the same time the most recent version is 4.13 already. This change is important to us because we want to move to 4.12 from 4.08 and the default GC behavior on 4.12 makes Z3 bindings a giant memory leak. 4.08 is a very stable version which brought a lot of new features people have come to depend on, so I think it's fine to establish it as a lower bound if it means people using more recent versions of OCaml can use Z3 at all.

The finalizer seemed useful in local tests to keep RAM low. The interplay between OCaml finalizers (beeing called in a arbitrary order) and internal Z3 cleaning with refcountings is highly non trivial, so I'm not sure what's up. This ensures that the deletion code is run at least once at the moment the corresponding object is collected.

@c-cube c-cube marked this pull request as draft October 14, 2021 17:41
@NikolajBjorner
Copy link
Contributor

NikolajBjorner commented Oct 14, 2021

If this works with you, very good.
These structures have shared sub-structures so their sizes aren't static. This can't be a unique problem to z3, but the size estimate interface I have seen so far (OCaml, .Net) are not equipped with some corresponding feature that would notify the gc.

For early projects such as Pex, we got much better performance by not relying on the GC.
It is then the client responsibility to not have any references to terms that are created within a scope.
Having the GC manage reference counts is a convenience, but anything that expects predictable behavior may as well manage their own regions (and lacking the grace of region type systems to provide guarantees, good luck debugging dangling pointers).

@NikolajBjorner NikolajBjorner marked this pull request as ready for review October 14, 2021 19:45
@NikolajBjorner NikolajBjorner merged commit 6302b86 into Z3Prover:master Oct 14, 2021
@c-cube
Copy link
Contributor Author

c-cube commented Oct 14, 2021

Ah well, I was still working on fine tuning it, I'll probably open a second PR. Sadly it's difficult in OCaml to perform manual memory management so we're stuck with the GC.

Unless there is a way we can modify the bindings to make this easier. I can imagine, for example, having a level of indirection internally, where there's a context_option object that is used everywhere (instead of having a direct pointer to the context), so that expressions, solvers, etc. only deallocate the context if it's not null. But maybe that's not compatible with how expressions are deallocated (if they truly need the context, to find subterms, say).

Another way would be to have an explicit notion of "region" where the context and solver and expressions would live, so we can deallocate them all at once.

@NikolajBjorner
Copy link
Contributor

Just open another PR.

The bare bones C API has it that expressions are pointers without pointers to contexts.
Contexts maintain hash-tables of expressions so need the reference decrement to point to a context. This is consistent with how dec_ref is exposed over the API, where it takes a context argument.

The "region" notion is already there in one way: you can allocate a context in a mode where expression lifetimes come with the context.

@c-cube
Copy link
Contributor Author

c-cube commented Oct 14, 2021

Just open another PR.

I will!

The "region" notion is already there in one way: you can allocate a context in a mode where expression lifetimes come with the context.

In that case, what I can use to refer to an expression through a context? Is there an integer key or something like that? That would be optimal for me.

@c-cube c-cube deleted the wip-ocaml-gc-custom-blocks branch October 15, 2021 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants