Skip to content

Mark arrays as collectable at runtime #9979

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

Closed
wants to merge 2 commits into from

Conversation

iluuu1994
Copy link
Member

@iluuu1994 iluuu1994 commented Nov 21, 2022

Arrays in this PR are marked with GC_NOT_COLLECTABLE by default. In arrays, only objects and references can actually contribute to a cycle because cycles with just arrays are impossible to create from userland. Once an object or reference is added to the array the GC_NOT_COLLECTABLE flag is removed. This patch does not re-add the flag when the potentially cyclic element is removed. This would be possible but would require tracking the number of elements on override/remove.

Benchmarks are yet to be done. This touches several hot-paths so we'll need to make sure it's worth it. JIT also needs an adjustment in one of the handlers which I'll look into. @dstogov I'd be very glad to hear your general thoughts before continuing to avoid working on something potentially non-feasible.


Btw, something similar might be possible for objects once dynamic properties gets fully removed.

When:

  • all properties of a class are typed
  • and all of those types are non cyclic, meaning anything but:
    • arrays
    • references to other cyclic classes

then the object of the given class doesn't need to be added to the gc buffer. The two could also be combined, meaning non-cyclic classes added to arrays can't make the arrays cyclic.

Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

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

The idea is interesting, but I afraid the additional checks may make slowdown instead of expected speedup. This should be carefully benchmarked.

Arrays are now marked with GC_NOT_COLLECTABLE by default. Only objects
and references can actually contribute to a cycle. Once an object or
reference is added to the array the GC_NOT_COLLECTABLE flag is removed.
This patch does never re-add the flag when the potentially cyclic
element is removed. This would be possible but would require tracking
the number of potentially elements on override/remove.
@iluuu1994
Copy link
Member Author

I tried benchmarking with Valgrind as well as with perf stat but the results seem unreliable. The results vary wildly between runs and thus it's hard to tell if the change has a positive or negative effect, if any at all. Tips on how to get more accurate measurements would be appreciated.

Symfony Demo /

ZEND_DONT_UNLOAD_MODULES=1 valgrind --tool=callgrind --separate-recs=1 --dump-instr=yes --cache-sim=no ../php-src/sapi/cgi/php-cgi -d zend_extension=$(pwd)/../php-src/modules/opcache.so -d opcache.enable=1 -T100 public/index.php > /dev/null

Before:
Elapsed time: 14.515504 sec
==504704== 
==504704== Events    : Ir
==504704== Collected : 3090273121
==504704== 
==504704== I   refs:      3,090,273,121

After:
Elapsed time: 14.826476 sec
==571964== 
==571964== Events    : Ir
==571964== Collected : 3099881221
==571964== 
==571964== I   refs:      3,099,881,221

Symfony Demo /de/blog/:

ZEND_DONT_UNLOAD_MODULES=1 valgrind --tool=callgrind --separate-recs=1 --dump-instr=yes --cache-sim=no ../php-src/sapi/cgi/php-cgi -d zend_extension=$(pwd)/../php-src/modules/opcache.so -d opcache.enable=1 -T100 public/index.php > /dev/null

Before:
Elapsed time: 45.554749 sec
==504502== 
==504502== Events    : Ir
==504502== Collected : 10604005060
==504502== 
==504502== I   refs:      10,604,005,060

After:
Elapsed time: 45.193365 sec
==440372== 
==440372== Events    : Ir
==440372== Collected : 10597199515
==440372== 
==440372== I   refs:      10,597,199,515

PHPStan

Before:
 Performance counter stats for '../php-src/sapi/cli/php -d memory_limit=2G -d zend_extension=/home/ilutov/Developer/phpstan-src/../php-src/modules/opcache.so -d opcache.enable_cli=1 bin/phpstan --debug':

        343,692.03 msec task-clock:u                     #    1.000 CPUs utilized          
                 0      context-switches:u               #    0.000 /sec                   
                 0      cpu-migrations:u                 #    0.000 /sec                   
           139,033      page-faults:u                    #  404.528 /sec                   
 1,183,713,485,667      cycles:u                         #    3.444 GHz                      (50.00%)
 1,765,963,438,942      instructions:u                   #    1.49  insn per cycle           (62.50%)
   177,913,785,437      branches:u                       #  517.655 M/sec                    (62.49%)
     3,210,747,790      branch-misses:u                  #    1.80% of all branches          (62.50%)
   649,128,225,069      L1-dcache-loads:u                #    1.889 G/sec                    (62.50%)
    17,876,842,843      L1-dcache-load-misses:u          #    2.75% of all L1-dcache accesses  (62.51%)
     3,982,013,044      LLC-loads:u                      #   11.586 M/sec                    (50.00%)
       360,078,544      LLC-load-misses:u                #    9.04% of all LL-cache accesses  (50.00%)

     343.785708808 seconds time elapsed

     339.094749000 seconds user
       0.827771000 seconds sys

After:
 Performance counter stats for '../php-src/sapi/cli/php -d memory_limit=2G -d zend_extension=/home/ilutov/Developer/phpstan-src/../php-src/modules/opcache.so -d opcache.enable_cli=1 bin/phpstan --debug':

        336,441.28 msec task-clock:u                     #    1.000 CPUs utilized          
                 0      context-switches:u               #    0.000 /sec                   
                 0      cpu-migrations:u                 #    0.000 /sec                   
           140,104      page-faults:u                    #  416.429 /sec                   
 1,162,996,306,873      cycles:u                         #    3.457 GHz                      (50.00%)
 1,768,527,792,843      instructions:u                   #    1.52  insn per cycle           (62.50%)
   178,133,087,555      branches:u                       #  529.463 M/sec                    (62.49%)
     3,187,759,190      branch-misses:u                  #    1.79% of all branches          (62.50%)
   650,433,979,507      L1-dcache-loads:u                #    1.933 G/sec                    (62.50%)
    17,495,596,566      L1-dcache-load-misses:u          #    2.69% of all L1-dcache accesses  (62.51%)
     3,987,081,054      LLC-loads:u                      #   11.851 M/sec                    (50.00%)
       304,078,180      LLC-load-misses:u                #    7.63% of all LL-cache accesses  (50.00%)

     336.497944515 seconds time elapsed

     331.160239000 seconds user
       1.020707000 seconds sys

Comment on lines +52 to +54
static zend_always_inline bool gc_ht_check_collectable(HashTable *ht) {
return (GC_FLAGS(ht) & (GC_PERSISTENT|GC_NOT_COLLECTABLE)) == GC_NOT_COLLECTABLE;
}
Copy link
Member

Choose a reason for hiding this comment

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

The name of the function is confusing. It doesn't tell what kind of check we do (positive or negative). I would propose to change it into gc_ht_is_not_collectable() or may be better into gc_ht_is_collectable() with corresponding condition changes.

@dstogov
Copy link
Member

dstogov commented Nov 28, 2022

It looks like the current implementation doesn't make significant effect. Some apps become a bit faster others are slower.

I think we should postpone this and think about non-collectable objects first. After disabling creation of dynamic object properties (they are deprecated in PHP-8.2) we may determine non-collectable classes just analysing their properties.

Then it would make sense to adopt gc_ht_value_may_cause_cycle() to support for non-collectable objects.

Also the GC algorithm might be improved. See the related ideas described at http://www.eng.usf.edu/~chang5/papers/12/RC_chang_12.pdf

@iluuu1994
Copy link
Member Author

@dstogov Thank you for taking the time to review this. That works for me, let's postpone this and see if it works better in combination with non-collectable objects, which won't require flagging at runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants