Skip to content

Add Ephemerons to OCaml#22

Merged
mshinwell merged 32 commits intoocaml:trunkfrom
bobot:feature/ephemerons
Jan 27, 2016
Merged

Add Ephemerons to OCaml#22
mshinwell merged 32 commits intoocaml:trunkfrom
bobot:feature/ephemerons

Conversation

@bobot
Copy link
Contributor

@bobot bobot commented Mar 25, 2014

Ephemerons

The current OCaml runtime proposes two kinds of pointers:

  • A points hardly to B: if A is alive, B is alive (usual pointers)
  • A points weakly to B: when the GC decide that B stops to be alive, A stops to point to B

Weak pointers are very handy when you want to be able to access a data without creating memory leaks. For example hashconsing can be done using the weaksets defined in Weak.Make. However you can't create general weaktable with the current runtime: associative table that keep a data until the key is not needed anymore. The problematic case is when the data points to the key(cf 10).

Barry Hayes proposed in 1997 (cf 9) the notion of ephemerons which allow to code such table. In its simplest form an ephemeron A with key K and data D keeps D alive while A and K are alive. To sum up ephemerons add the possibility to express conjunctions where you normally have only disjunctions.

Request for Comment and Merge-Request

This merge-request adds ephemerons to ocaml with particularly a modification of the runtime, 4, and a new module in the standard library, 6. An untyped API with an arbitrary number of keys is given in Ephemerons.Obj and two specialized API for one and two keys are provided in Ephemerons.K1 and Ephemerons.K2. Each of these three APIs define a functor for constructing weaktables with the same signature than Hashtbl.

Before an actual review of the C or OCaml implementation parts, comments about the OCaml interface are more than welcomed.

The modifications are splitted as follows:

  • 1, 2: code factorization that can be removed from the merge request but at the end without them some functions start to be very big
  • 3: A correction of the behavior of the weak pointer when more than one weak array points to one value
  • 4: Add the ephemerons in the runtime (simple but inefficient way). The stdlib is modified in another commit in order to simplify rebase and bootstrapping.
  • 5: just add a getter for Hashtbl.randomized
  • 6: Add the module Ephemeron in the stdlib and some tests in the testsuites
  • 7: Shortcut clean phase if possible (can be removed from the merge request)
  • 8: Optimize the implementation of the ephemerons and mitigate the worst case

The code have been evaluated on Why3, "don't use" correspond to why3 commit 11 and "use" to why3 commit 12 that replace the half-satisfying weak-table solution by weak-table provided by Ephemeron.

0 1 2 3 4 5 6 7 8
don't use 12.96 (0.02) 12.96 (0.13) 12.93 (0.03) 13.26 (0.06) 13.70 (0.27) 13.87 (0.16) 13.30 (0.04) 13.37 (0.10) 13.10 (0.09)
use NA NA NA NA NA NA 12.08 (0.06) 12.02 (0.05) 11.88 (0.05)

We can see that the slowdown for a code that heavily use weak pointers is of 1-2%, and the speedup when using weak-pointers (weaksets) and weaktable is of 8% without counting the fact that the weaktable is "perfect" and simple to use.

nb: the branch is not directly mergeable because of boot/ocaml..., but at the end of the review process I will rebase the branch and doing the bootstrap.

How to reproduce the bench

clone why3, configure --enable-local, bin/why3config --detect. Add this section to why3.conf:

[prover]
command = "/bin/true %t %f"
driver = "drivers/null_smt.drv"
editor = ""
in_place = false
interactive = false
name = "null"
version = "0"

Bench with the command:

(exec bin/why3session.opt output --output /tmp --filter-prover null $(git ls-files "examples/**/why3session.xml") -L examples/bitvectors -F -L examples/vacid_0_binary_heaps/) >& /dev/null

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo bu -> by

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx, the fix will be autosquashed

@bobot
Copy link
Contributor Author

bobot commented Mar 26, 2014

About the slowdown. In the patch of 2011, ephemerons used a specific tag and so codes that didn't use ephemerons where not impacted. Here weak pointers are implemented using ephemerons (one additional word) and none of them use a specific tag. I tried to reduce the slowdown but any attempt had the opposite effect. The only way I can think of is to separate in two different lists ephemerons and weak pointers. Currently you can go from one to the other, a weak pointer is just an ephemeron with an empty data.

@alainfrisch
Copy link
Contributor

I had wished for inclusion of ephemerons for a long time, and it's great to see some progress in this direction!

For users who don't care about the seed property, would it make sense to expose "Make" functors (with hash: t -> int), assuming this can be done without too much code bloat?

I'm also slightly reluctant to keeping Ephemeron.Obj in the same unit as Ephemeron. What about moving it to a submode of Obj (hence: Obj.Ephemeron), or creating a different unit?

The documentation of Ephemeron.Obj.create should explain the meaning of the argument (number of keys).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor typo: "This function as" -> "This function has"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbuenzli
Copy link
Contributor

Those are only documentation comments:

  1. I think the documentation of K1.MakeSeeded and K2.MakeSeeded should make it more clear to the casual programmer what they actually build (i.e. IIUC a hashtable were both keys and values are weak: whenever one or the other becomes unreachable the binding automagically disappear from the hashtable)
  2. We now have two notion of weak hashtables in the stdlib: those of Weak and those of Ephemeron. I think they should be somehow distinguished from a terminological point of view to ease our programming discussions. I would call Weak's current hashtables, weak hashed sets (as I think to most programmers mind hashtables are understood as being maps). In that case Weak's doc should be updated to reflect that.
  3. The toplevel comment of the module says ephemeron and weak tables. That's the only use of weak table, maybe this should be changed to ephemeron and weak hash tables, I was looking for those unhashed tables but didn't find them.

Other than that:

  1. I share the sentiment of Alain that the unsafe stuff should go in Obj. A toplevel Obj is immediately suspicious and the spot of attention in case of hard problems.
  2. I would be curious to test the slowdown on a real world React code base but unfortunately I don't have that at hand.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't that rather be is_randomized ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually I use get_A, set_A, is_A. But perhaps here is_randomized is better, if someone else agree I will change that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_randomized is a better name in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bobot
Copy link
Contributor Author

bobot commented Mar 26, 2014

The last push took, I hope, into account the majority of the current remarks:

@alainfrisch :

  1. Add Make functor: bobot/ocaml@178c475
  2. I hesitated but since you and dbuenzil agree, I will put Ephemeron.Obj in Obj.Ephemeron
  3. Ephemeron.obj.create fixed: bobot/ocaml@20e9cf9

@dbuenzli doc:

  1. The values are not really weak since they are strong if the keys are alive. I added more explanation in bobot/ocaml@beed79b
  2. Weak.Make is indeed hash set. bobot/ocaml@9bc9ab5 fix that.
  3. Unhashed weak table can be fun :) but I fixed the missing "hash" in bobot/ocaml@f622cad

@dbuenzli code:

  1. I will do that. But for the typed version should I add K3, K4,... until when?
  2. I would love to see that, perhaps the new opam switch can simplify that.

@bobot
Copy link
Contributor Author

bobot commented Mar 26, 2014

I added a note about marshalling in bobot/ocaml@ec11031

@alainfrisch
Copy link
Contributor

Where does the restriction about marshaling come from?

@bobot
Copy link
Contributor Author

bobot commented Mar 26, 2014

Weak pointers already have this restriction.I think the problems are:

  • Weak and ephemerons use the Abstract Tag, so special code must be written.
  • I don't know during input how to recognize them in order to add them to the linked list caml_ephe_list_head.
  • That makes sense only if you marshal with sharing.

But if you are guided by the type and you keep the sharing there is no problem, I think.

Another point I think we can lift the restriction about integer in Weak and Ephemeron. We can say that an integer value is always alive. That could remove some complication in the case of int Lazy.t Weak.t where the shortcut is forbidden. I can try to write the code after a first review of this merge-request or before if needed

@bobot
Copy link
Contributor Author

bobot commented Mar 27, 2014

Ephemeron.Obj moved in Obj.Ephemeron in commit bobot/ocaml@b0f6a7b

@damiendoligez
Copy link
Member

About integers, the first version of Weak allowed integers as always-live values but then people complained that the integers they put in their weak hash sets would never disappear, so I removed them. I don't think it's a good idea to add them.

@damiendoligez
Copy link
Member

I just added commit dc49f2e to remove code duplication between caml_ref_table and caml_ephe_ref_table. Needs to be reviewed.

@bobot
Copy link
Contributor Author

bobot commented Apr 2, 2014

Since Ephemeron.Obj is now in Obj.Ephemeron there is no ephemeron with arbitrary arity in Ephemeron. So I propose to add ephemerons Ephemeron.Kn.t with an arbitrary number of key with the same type [bobot/ocaml@e39fa5d](sorry the implementation is in [bobot/ocaml@0a3c081]). The module Ephemeron.K can be reserved for when somebody finds a nice interface for heterogeneous typed ephemerons with arbitrary arity.

lpw25 pushed a commit to lpw25/ocaml that referenced this pull request Oct 1, 2014
Move examples to the test suite
stedolan added a commit to stedolan/ocaml that referenced this pull request Aug 18, 2015
Reset realloc'ed stack to avoid useless scanning.
@gasche
Copy link
Member

gasche commented Nov 19, 2015

We would like to merge this in trunk pretty soon, but @bobot needs time to rebase on top of the current trunk. I think the plan is to actually rebase on top of #297 (rather than the other way around).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In stats_alive: Why is the number of bindings in bucket b counted via the bucket_length function?
Is the function expected to compute the number of bindings that are still alive? In that case, should the call to bucket_length be replaced by a call of bucket_length_alive?
I expect this would produce the actual number of bindings that are still alive within the bucket?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted. In fact the new warnings used during the compilation of the trunk also found it. Fixed.

@bobot bobot force-pushed the feature/ephemerons branch 2 times, most recently from a6883e0 to bc77353 Compare November 29, 2015 16:36
@bobot
Copy link
Contributor Author

bobot commented Nov 29, 2015

@damiendoligez The branch is rebased on trunk. There is no big differences compared to before, more comments, I hope better naming. Just c038923, which factorize the short-circuiting of Forward tag, have been heavily modified. We discussed it before, but I think we forget that there are different use of it. I used flags for selecting the different mode. What do you think of it?

I need to review #297 before rebasing above it.

Some general remarks:

  • alloc_generic_table was not marked as static in a previous version of the branch, so it was exported in byterun/libcamlrun.a. Perhaps we could check during make tests that no functions are exported without the camlprefixed. Currently two functions seems wronly exported.
# nm -g --defined-only -A byterun/libcamlrun.a | grep -v " caml"
byterun/libcamlrun.a:backtrace_prim.o:0000000000000020 T process_debug_events
byterun/libcamlrun.a:backtrace_prim.o:0000000000000810 T read_main_debug_info
byterun/libcamlrun.a:debugger.o:0000000000000010 D marshal_flags
byterun/libcamlrun.a:main.o:0000000000000000 T main
  • Git Tips that I learned the hard way, if one have many fixup:... for fixing commits, you should rebase them one by one.
  • Why do we have to modify boot/ocamlc when adding primitive? Can't we have the information about the primitive in a separate generated text file that will be read by boot/ocamlc? So that these binaries that can't be merged automatically have to be modified less often?

@mshinwell
Copy link
Contributor

I only spent two minutes looking at this patch, but I have two comments:

  1. Have you ensured that the refactoring in the major GC does not cause a performance degradation? In particular, I don't think the "inline" keyword is sufficient to force inlining of that function. I think for gcc you can ensure that using __attribute__((always_inline)) or something like that, though I don't remember offhand.
  2. I suggest Obj.Ephemeron.eph be called Obj.Ephemeron.t instead.

@bobot
Copy link
Contributor Author

bobot commented Dec 1, 2015

@mshinwell For point 1. I have not read the assembly code obtained to see if the refactoring is inlined. However in the performance test I've done (in the header of this MR) there is no regression for the first commits (0,1,2). If people prefer I can try to force inlining.

For point 2. You are completely right, I do the substitution

By mail I received some comments for the documentation of the ephemerons by William (Romait Troit) that I will take into account shortly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: phaese -> phase

@bobot
Copy link
Contributor Author

bobot commented Apr 12, 2016

The MR #515 add the missing changes and modification of the documentation. (It target 4.03 but is tagged 4.04 because it contained other more invasive changes)

@lpw25
Copy link
Contributor

lpw25 commented Apr 12, 2016

Hmm. Whilst that change does break the current code, I cannot see how it would cause the specific test failure. Has anything else changed about when weak pointers are cleared? It seems like a single Gc.full_major was previously enough to clear them in the test, but now two Gc.full_majors are required.

@bobot
Copy link
Contributor Author

bobot commented Apr 12, 2016

If there is a finalizer attached to it you need indeed two rounds. In the first the value is marked as to be finalized and alive ( since the finalizer will use the value). In the second it is marked dead and so can be removed from the weak pointer and swept.

Previously you needed only one round for the value to be removed from the weak pointer, but two rounds for the value to be swept. Since for fixing the semantic of weak pointer the value is removed from weak pointer only when dead so swept, it is done in the second round.

@lpw25
Copy link
Contributor

lpw25 commented Apr 15, 2016

I have to say that this change to the ordering between finalisers and weak references is quite annoying. It is not now possible to get something to run after a weak has been emptied, which makes it hard to tidy up the data structure (e.g. hashtable) which contains the now empty weak reference.

I considered trying to replace the weak reference with an ephemeron pointing to a fresh block, and then place the finaliser on that block instead of on the target of the weak reference -- but looking through the patch it is clear that the finaliser may still run before we have entered the clean phase which means the ephemeron will not yet be cleared.

I appreciate the reasons for changing the order, but it would have been good to simultaneously add support for the other kind of finalisers (i.e. the ones which do not receive their target as an argument and so can safely be run after weak has been cleared) since it is now quite difficult to handle clean up of data structures containing weak pointers.

@lpw25
Copy link
Contributor

lpw25 commented Apr 15, 2016

As a side note, whilst looking around the patch, it seemed to me that line 479 of major_gc.c:

ephes_to_check = ephes_checked_if_pure;

(the one just after calling caml_final_update) is incorrect.

  • If caml_final_update did not darken any new values then we do not need to check the ephemerons again.
  • If caml_final_update did darken some new values then caml_darken would have set ephe_list_pure back to 0. This means that we will start checking the ephemerons again with ephe_list_pure set to 0 rather than 1, so even if none of the ephemerons were effected by the newly dark values we will end up scanning them twice.

Either way it looks like it will tend to scan the ephemeron list one more time than is necessary.

Simply removing that line would fix it.

@damiendoligez
Copy link
Member

@lpw25

I have to say that this change to the ordering between finalisers and weak references is quite annoying. It is not now possible to get something to run after a weak has been emptied, which makes it hard to tidy up the data structure (e.g. hashtable) which contains the now empty weak reference.

I want to keep this problem on the radar. Could you open a Mantis PR?

@bobot
Copy link
Contributor Author

bobot commented Apr 18, 2016

@damiendoligez Doesn't MPR 7210 is enough for that ?

@lpw25 Do you tried to use Ephemerons.K1.Make or the same implementation since you have some additional functions ? I think the size of Ephemerons.K1.Make table will be at most twice the size of your table. A cleanup is done before resizing and the resizing is aborted if after cleanup the current number of element is less than half the current size. That keeps the constant amortized time complexity.

I appreciate the reasons for changing the order, but it would have been good to simultaneously add support for the other kind of finalizer.

I agree. It will be too late for 4.03 but for 4.04 I'm willing to propose patch to add the support if it is seen as needed in MPR 7210.

I considered trying to replace the weak reference with an ephemeron pointing to a fresh block, and then place the finalizer on that block instead of on the target of the weak reference -- but looking through the patch it is clear that the finalizer may still run before we have entered the clean phase which means the ephemeron will not yet be cleared.

It is even more sure than not, if I understand what you do correctly. The ephemeron is unset in the same gc round than the key is swept, so at least one round after the "finalization" of the key.

As a side note, whilst looking around the patch, it seemed to me that line 479 of major_gc.c

It is an interesting remark. I agree that it is unnecessary. I will do the GPR for 4.04.

@damiendoligez
Copy link
Member

Doesn't MPR 7210 is enough for that ?

Yes it is, thanks.

anmolsahoo25 pushed a commit to anmolsahoo25/ocaml that referenced this pull request Aug 25, 2020
Always update budget_left in major_collection_slice
poechsel pushed a commit to poechsel/ocaml that referenced this pull request Jul 2, 2021
* Add intrinsics for ext_pointer and native_pointer

* Add two_args

* remove XCR

* Add intrinisics for storing and loading int64,int32,nativeint

Fix up names

* Remove "unsafe" from the name of one of the intrinsics

* Address review comments

Use Word_int instead of Word_val for {load,store}_immediate intrinsics

Co-authored-by: Mark Shinwell <[email protected]>
stedolan pushed a commit to stedolan/ocaml that referenced this pull request May 24, 2022
ce88833 Merge flambda-backend changes
b7506bb Revert "Cherry-pick of ocaml/ocaml 1eeb0e7 (ocaml#12)"
183f688 Add config option to enable/disable stack allocation (ocaml#22)
ee7c849 If both the type and mode of an ident are wrong, complain about the type. (ocaml#19)
44bade0 Allow submoding during module inclusion checks (ocaml#21)
de3bec9 Add subtyping between arrows of related modes (ocaml#20)
93d8615 Enable the local keywords even when the local extension is off (ocaml#18)
81dd85e Documentation for local allocations
b05519f Fix a GC bug in local stack scanning (ocaml#17)
9f879de Fix __FUNCTION__ (ocaml#15)
a78975e Optimise "include struct ... end" in more cases (ocaml#11134)
b819c66 Cherry-pick of ocaml/ocaml 1eeb0e7 (ocaml#12)
bb363d4 Optimise the allocation of optional arguments (ocaml#11)

git-subtree-dir: ocaml
git-subtree-split: ce88833
lpw25 added a commit to lpw25/ocaml that referenced this pull request Jun 21, 2022
* Add config option to enable/disable stack allocation

The local extension can now be turned on independently of whether
anything actually gets allocated on the stack.
EmileTrotignon pushed a commit to EmileTrotignon/ocaml that referenced this pull request Jan 12, 2024
* also start using unicode strings and workaround for next/link not working with react child components
EmileTrotignon pushed a commit to EmileTrotignon/ocaml that referenced this pull request Jan 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.