Skip to content

Conversation

arnaud-lb
Copy link
Owner

@arnaud-lb arnaud-lb commented Sep 18, 2025

This is a rough PoC of using the Boehm GC1 as only garbage collection mechanism for userland internal structures.

Switching to a tracing GC has the following potential benefits:

  • Prevents use-after-free bugs
  • According to 2, refcounting + backup tracing GC can be faster than refcounting + cycle collection
  • Major code simplification if we could eliminate refcounting entirely

What this branch does:

  • Replace Zend MM and malloc()3 by libgc's allocation API
  • Disable the cycle collector
  • Make e?free() a no-op
  • Remove reference counting on objects
  • Keep reference counting other values, for semantic purposes only

Refcounting

Removing refcounting entirely would bring some performance benefits and considerable code simplifications, but we still need it for two things:

  • Provide CoW semantics on arrays and strings (separate array/string if refcount > 1 before mutation)
  • Calling destructors and closing resources in a timely / predictable manner

For arrays, one solution would be to switch to a functional/persistent data structure.

For destructors and resources there is no ideal solution apart from making resource closing more explicit in applications. Mechanisms like "with" / context managers would help.

Finalization

Objects need to perform some actions before they are released, like calling destructors, calling custom dtor/free handlers, or releasing weak refs.

We rely on libgc's finalization mechanism to be notified when an object is about to be collected. In particular, we use unordered java finalization, which matches what the cycle collector does already.

Performance

  • This is 2-25% faster than baseline on the symfony demo benchmark depending on settings
  • This is more than 30% faster than baseline on the unit of work benchmark. However, this is 8% slower when a finalizer is registered on every object, for two reasons: 1. Normally the GC scans only live objects; finalizers require to scan unreachable objects too; 2. Registering finalizers is slow. Finalizers are required to keep refcounting accurate. Without refcounting, finalizers are only required for objects with a destructor.

Potential improvements:

  • Provide type information4 for some allocations. At least for arrays, to skip the hashtable part. The heap is 30% arrays in the symfony benchmark. Also for some structures like realpath_cache_bucket that end with non-ptr data.
  • We could save some mm usage with -DDONT_ADD_BYTE_AT_END if we don't point to the end of objects (e.g. arg_info for functions with a return type but no args).
  • GC_REGISTER_FINALIZER is slow and duplicates the tracking done by the objects store. It may be possible to implement finalization without GC_REGISTER_FINALIZER, or to remove the objects store.
  • Update ZEND_MM_OVERHEAD

Incremental GC:

  • libgc supports an incremental mode, that can be enabled at runtime. This should reduce the average GC pause time, but this reduces throughput.

Building

libgc:

# debug
CFLAGS="-ggdb3 -O0 -DGC_ON_GROW_LOG_SIZE_MIN=100000 -DKEEP_BACK_PTRS=1 -DGC_ASSERTIONS=1 -DDBG_HDRS_ALL=1" ./configure --disable-threads --prefix=/opt/bdwgc-dbg/

# release
CFLAGS="-ggdb3 -O2 -DGC_ON_GROW_LOG_SIZE_MIN=100000" ./configure --disable-threads --prefix=/opt/bdwgc/

php:

bdwgc_prefix=/opt/bdwgc
CFLAGS="$CFLAGS -I$bdwgc_prefix/include -DUSE_LIBGC" \
CXXFLAGS="$CXXFLAGS -I$bdwgc_prefix/include -DUSE_LIBGC" \
LDFLAGS="$LDFLAGS -L$bdwgc_prefix/lib -lgc -Wl,-rpath,$bdwgc_prefix/lib" \
./configure --disable-phpdbg --disable-zend-test --disable-dl-test --disable-debug

Caveats / TODO

  • Out of the box, libgc is not compatible with ZTS. It can be thread safe, but it can not have a separate heap per thread, with its own finalizers and memory limit.
  • ASAN in libgc is broken (doesn't root the ASAN stack).

Footnotes

  1. https://www.hboehm.info/gc/ / https://github.com/bdwgc/bdwgc

  2. https://www.researchgate.net/publication/2926551_On-the-Fly_Cycle_Collection_Revisited

  3. Only in php-src

  4. https://github.com/bdwgc/bdwgc/blob/master/include/gc/gc_typed.h

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.

1 participant