Skip to content

Commit d073193

Browse files
authored
Expose time spent collecting cycles in gc_status() (#11523)
1 parent 08b5777 commit d073193

16 files changed

+273
-194
lines changed

UPGRADING

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,20 @@ PHP 8.3 UPGRADE NOTES
146146
========================================
147147

148148
- Core:
149-
. gc_status() has added the following 4 fields:
149+
. gc_status() has added the following 8 fields:
150150
"running" => bool
151151
"protected" => bool
152152
"full" => bool
153153
"buffer_size" => int
154-
See GH-9336
154+
"application_time" => float: Total application time, in seconds (including
155+
collector_time)
156+
"collector_time" => float: Time spent collecting cycles, in seconds
157+
(including destructor_time and free_time)
158+
"destructor_time" => float: Time spent executing destructors during
159+
cycle collection, in seconds
160+
"free_time" => float: Time spent freeing values during cycle collection, in
161+
seconds
162+
See GH-9336, GH-11523
155163
. class_alias() now supports creating an alias of an internal class.
156164
. Setting `open_basedir` at runtime using `ini_set('open_basedir', ...);` no
157165
longer accepts paths containing the parent directory (`..`). Previously,

UPGRADING.INTERNALS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ PHP 8.3 INTERNALS UPGRADE NOTES
6767
- zend_set_user_opcode_handler
6868
- zend_ssa_inference
6969
* Removed unused macros PHP_FNV1_32A_INIT and PHP_FNV1A_64_INIT. See GH-11114.
70+
* ext/standard/hrtime.h was moved to Zend/zend_hrtime.h
71+
* The prefix of the PHP_HRTIME_ macros was changed to ZEND_HRTIME_
72+
* The HRTIME_AVAILABLE macro was renamed to ZEND_HRTIME_AVAILABLE
73+
* The php_hrtime_current() function was renamed to zend_hrtime()
7074
* _php_stream_dirent now has an extra d_type field that is used to store the
7175
directory entry type. This can be used to avoid additional stat calls for
7276
types when the type is already known.

Zend/tests/gc_037.phpt

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ gc_collect_cycles();
1212
gc_collect_cycles();
1313
var_dump(gc_status());
1414
?>
15-
--EXPECT--
16-
array(8) {
15+
--EXPECTF--
16+
array(12) {
1717
["running"]=>
1818
bool(false)
1919
["protected"]=>
@@ -30,8 +30,16 @@ array(8) {
3030
int(16384)
3131
["roots"]=>
3232
int(1)
33+
["application_time"]=>
34+
float(%f)
35+
["collector_time"]=>
36+
float(%f)
37+
["destructor_time"]=>
38+
float(%f)
39+
["free_time"]=>
40+
float(%f)
3341
}
34-
array(8) {
42+
array(12) {
3543
["running"]=>
3644
bool(false)
3745
["protected"]=>
@@ -48,4 +56,12 @@ array(8) {
4856
int(16384)
4957
["roots"]=>
5058
int(0)
59+
["application_time"]=>
60+
float(%f)
61+
["collector_time"]=>
62+
float(%f)
63+
["destructor_time"]=>
64+
float(%f)
65+
["free_time"]=>
66+
float(%f)
5167
}

Zend/tests/gc_045.phpt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ for ($j = 0; $j < 10; $j++) {
4545

4646
var_dump(gc_status());
4747
?>
48-
--EXPECT--
49-
array(8) {
48+
--EXPECTF--
49+
array(12) {
5050
["running"]=>
5151
bool(false)
5252
["protected"]=>
@@ -63,4 +63,12 @@ array(8) {
6363
int(16384)
6464
["roots"]=>
6565
int(10000)
66+
["application_time"]=>
67+
float(%f)
68+
["collector_time"]=>
69+
float(%f)
70+
["destructor_time"]=>
71+
float(%f)
72+
["free_time"]=>
73+
float(%f)
6674
}

Zend/zend.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "zend_fibers.h"
3838
#include "zend_call_stack.h"
3939
#include "zend_max_execution_timer.h"
40+
#include "zend_hrtime.h"
4041
#include "Optimizer/zend_optimizer.h"
4142

4243
static size_t global_map_ptr_last = 0;
@@ -899,6 +900,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */
899900
fpsetmask(0);
900901
#endif
901902

903+
zend_startup_hrtime();
902904
zend_startup_strtod();
903905
zend_startup_extensions_mechanism();
904906

Zend/zend_builtin_functions.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ ZEND_FUNCTION(gc_status)
138138

139139
zend_gc_get_status(&status);
140140

141-
array_init_size(return_value, 8);
141+
array_init_size(return_value, 16);
142142

143143
add_assoc_bool_ex(return_value, "running", sizeof("running")-1, status.active);
144144
add_assoc_bool_ex(return_value, "protected", sizeof("protected")-1, status.gc_protected);
@@ -148,6 +148,12 @@ ZEND_FUNCTION(gc_status)
148148
add_assoc_long_ex(return_value, "threshold", sizeof("threshold")-1, (long)status.threshold);
149149
add_assoc_long_ex(return_value, "buffer_size", sizeof("buffer_size")-1, (long)status.buf_size);
150150
add_assoc_long_ex(return_value, "roots", sizeof("roots")-1, (long)status.num_roots);
151+
152+
/* Using double because zend_long may be too small on some platforms */
153+
add_assoc_double_ex(return_value, "application_time", sizeof("application_time")-1, (double) status.application_time / ZEND_NANO_IN_SEC);
154+
add_assoc_double_ex(return_value, "collector_time", sizeof("collector_time")-1, (double) status.collector_time / ZEND_NANO_IN_SEC);
155+
add_assoc_double_ex(return_value, "destructor_time", sizeof("destructor_time")-1, (double) status.dtor_time / ZEND_NANO_IN_SEC);
156+
add_assoc_double_ex(return_value, "free_time", sizeof("free_time")-1, (double) status.free_time / ZEND_NANO_IN_SEC);
151157
}
152158
/* }}} */
153159

Zend/zend_gc.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#include "zend.h"
7070
#include "zend_API.h"
7171
#include "zend_fibers.h"
72+
#include "zend_hrtime.h"
7273

7374
#ifndef ZEND_GC_DEBUG
7475
# define ZEND_GC_DEBUG 0
@@ -220,6 +221,11 @@ typedef struct _zend_gc_globals {
220221
uint32_t gc_runs;
221222
uint32_t collected;
222223

224+
zend_hrtime_t activated_at;
225+
zend_hrtime_t collector_time;
226+
zend_hrtime_t dtor_time;
227+
zend_hrtime_t free_time;
228+
223229
#if GC_BENCH
224230
uint32_t root_buf_length;
225231
uint32_t root_buf_peak;
@@ -439,6 +445,10 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
439445

440446
gc_globals->gc_runs = 0;
441447
gc_globals->collected = 0;
448+
gc_globals->collector_time = 0;
449+
gc_globals->dtor_time = 0;
450+
gc_globals->free_time = 0;
451+
gc_globals->activated_at = 0;
442452

443453
#if GC_BENCH
444454
gc_globals->root_buf_length = 0;
@@ -479,6 +489,10 @@ void gc_reset(void)
479489
GC_G(gc_runs) = 0;
480490
GC_G(collected) = 0;
481491

492+
GC_G(collector_time) = 0;
493+
GC_G(dtor_time) = 0;
494+
GC_G(free_time) = 0;
495+
482496
#if GC_BENCH
483497
GC_G(root_buf_length) = 0;
484498
GC_G(root_buf_peak) = 0;
@@ -488,6 +502,8 @@ void gc_reset(void)
488502
GC_G(zval_marked_grey) = 0;
489503
#endif
490504
}
505+
506+
GC_G(activated_at) = zend_hrtime();
491507
}
492508

493509
ZEND_API bool gc_enable(bool enable)
@@ -1469,6 +1485,8 @@ ZEND_API int zend_gc_collect_cycles(void)
14691485
bool should_rerun_gc = 0;
14701486
bool did_rerun_gc = 0;
14711487

1488+
zend_hrtime_t start_time = zend_hrtime();
1489+
14721490
rerun_gc:
14731491
if (GC_G(num_roots)) {
14741492
int count;
@@ -1482,6 +1500,7 @@ ZEND_API int zend_gc_collect_cycles(void)
14821500
stack.next = NULL;
14831501

14841502
if (GC_G(gc_active)) {
1503+
GC_G(collector_time) += zend_hrtime() - start_time;
14851504
return 0;
14861505
}
14871506

@@ -1561,6 +1580,7 @@ ZEND_API int zend_gc_collect_cycles(void)
15611580
*
15621581
* The root buffer might be reallocated during destructors calls,
15631582
* make sure to reload pointers as necessary. */
1583+
zend_hrtime_t dtor_start_time = zend_hrtime();
15641584
idx = GC_FIRST_ROOT;
15651585
while (idx != end) {
15661586
current = GC_IDX2PTR(idx);
@@ -1582,11 +1602,13 @@ ZEND_API int zend_gc_collect_cycles(void)
15821602
}
15831603
idx++;
15841604
}
1605+
GC_G(dtor_time) += zend_hrtime() - dtor_start_time;
15851606

15861607
if (GC_G(gc_protected)) {
15871608
/* something went wrong */
15881609
zend_get_gc_buffer_release();
15891610
zend_fiber_switch_unblock();
1611+
GC_G(collector_time) += zend_hrtime() - start_time;
15901612
return 0;
15911613
}
15921614
}
@@ -1595,6 +1617,7 @@ ZEND_API int zend_gc_collect_cycles(void)
15951617

15961618
/* Destroy zvals. The root buffer may be reallocated. */
15971619
GC_TRACE("Destroying zvals");
1620+
zend_hrtime_t free_start_time = zend_hrtime();
15981621
idx = GC_FIRST_ROOT;
15991622
while (idx != end) {
16001623
current = GC_IDX2PTR(idx);
@@ -1645,6 +1668,8 @@ ZEND_API int zend_gc_collect_cycles(void)
16451668
current++;
16461669
}
16471670

1671+
GC_G(free_time) += zend_hrtime() - free_start_time;
1672+
16481673
zend_fiber_switch_unblock();
16491674

16501675
GC_TRACE("Collection finished");
@@ -1666,6 +1691,7 @@ ZEND_API int zend_gc_collect_cycles(void)
16661691
finish:
16671692
zend_get_gc_buffer_release();
16681693
zend_gc_root_tmpvars();
1694+
GC_G(collector_time) += zend_hrtime() - start_time;
16691695
return total_count;
16701696
}
16711697

@@ -1679,6 +1705,10 @@ ZEND_API void zend_gc_get_status(zend_gc_status *status)
16791705
status->threshold = GC_G(gc_threshold);
16801706
status->buf_size = GC_G(buf_size);
16811707
status->num_roots = GC_G(num_roots);
1708+
status->application_time = zend_hrtime() - GC_G(activated_at);
1709+
status->collector_time = GC_G(collector_time);
1710+
status->dtor_time = GC_G(dtor_time);
1711+
status->free_time = GC_G(free_time);
16821712
}
16831713

16841714
ZEND_API zend_get_gc_buffer *zend_get_gc_buffer_create(void) {

Zend/zend_gc.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#ifndef ZEND_GC_H
2121
#define ZEND_GC_H
2222

23+
#include "zend_hrtime.h"
24+
2325
#ifndef GC_BENCH
2426
# define GC_BENCH 0
2527
#endif
@@ -35,6 +37,10 @@ typedef struct _zend_gc_status {
3537
uint32_t threshold;
3638
uint32_t buf_size;
3739
uint32_t num_roots;
40+
zend_hrtime_t application_time;
41+
zend_hrtime_t collector_time;
42+
zend_hrtime_t dtor_time;
43+
zend_hrtime_t free_time;
3844
} zend_gc_status;
3945

4046
ZEND_API extern int (*gc_collect_cycles)(void);

Zend/zend_hrtime.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Niklas Keller <kelunik@php.net> |
14+
| Author: Anatol Belski <ab@php.net> |
15+
+----------------------------------------------------------------------+
16+
*/
17+
18+
#include "zend.h"
19+
#include "zend_hrtime.h"
20+
21+
/* This file reuses code parts from the cross-platform timer library
22+
Public Domain - 2011 Mattias Jansson / Rampant Pixels */
23+
24+
#if ZEND_HRTIME_PLATFORM_POSIX
25+
26+
# include <unistd.h>
27+
# include <time.h>
28+
# include <string.h>
29+
30+
#elif ZEND_HRTIME_PLATFORM_WINDOWS
31+
32+
# define WIN32_LEAN_AND_MEAN
33+
34+
double zend_hrtime_timer_scale = .0;
35+
36+
#elif ZEND_HRTIME_PLATFORM_APPLE
37+
38+
# include <mach/mach_time.h>
39+
# include <string.h>
40+
mach_timebase_info_data_t zend_hrtime_timerlib_info = {
41+
.numer = 0,
42+
.denom = 1,
43+
};
44+
45+
#elif ZEND_HRTIME_PLATFORM_HPUX
46+
47+
# include <sys/time.h>
48+
49+
#elif ZEND_HRTIME_PLATFORM_AIX
50+
51+
# include <sys/time.h>
52+
# include <sys/systemcfg.h>
53+
54+
#endif
55+
56+
void zend_startup_hrtime(void)
57+
{
58+
#if ZEND_HRTIME_PLATFORM_WINDOWS
59+
60+
LARGE_INTEGER tf = {0};
61+
if (QueryPerformanceFrequency(&tf) || 0 != tf.QuadPart) {
62+
zend_hrtime_timer_scale = (double)ZEND_NANO_IN_SEC / (zend_hrtime_t)tf.QuadPart;
63+
}
64+
65+
#elif ZEND_HRTIME_PLATFORM_APPLE
66+
67+
mach_timebase_info(&zend_hrtime_timerlib_info);
68+
69+
#endif
70+
}

0 commit comments

Comments
 (0)