Zend: Preallocate error buffer with capacity tracking#20565
Zend: Preallocate error buffer with capacity tracking#20565devnexen wants to merge 4 commits intophp:masterfrom
Conversation
|
It's often a good idea to choose a factor of 1.5, there's a mathematical reason behind it (https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md#memory-handling). |
|
yes will try ; even though usage and growth might not be that as critical as your (nice) link contexts. |
Interesting, we currently extend arrays by a factor of 2, I wonder what would happen if we do 1.5 instead. |
Won't work because the hash masking depends on it being a power of 2. |
|
Can we get a clearer PR title? |
Zend/zend.c
Outdated
| * Use pow2 realloc if it becomes a problem. */ | ||
| EG(num_errors)++; | ||
| EG(errors) = erealloc(EG(errors), sizeof(zend_error_info*) * EG(num_errors)); | ||
| // pondering if uint32_t is more appropriate but would need to align the allocation size then |
There was a problem hiding this comment.
Storing the size like this is very ugly in my opinion. What you could do is make a struct with a size field and a flexible array member at the end.
d9f84e7 to
c6bed1f
Compare
f38b0e3 to
078130f
Compare
21c2fdc to
5111ca5
Compare
ndossche
left a comment
There was a problem hiding this comment.
When dealing with nested fields in module globals, we normally use the EG(errors)->size etc syntax instead of EG(errors->size). Please change this.
I'm not sure how I feel about unconditionally allocating a buffer in the executor globals. I understand this is to allow unconditional access to fields like EG(errors)->size ? I see previously it was always safe to access EG(num_errors).
Zend/zend_globals.h
Outdated
| HashTable callable_convert_cache; | ||
|
|
||
| void *reserved[ZEND_MAX_RESERVED_RESOURCES]; | ||
| struct zend_err_buf *errors; |
There was a problem hiding this comment.
I don't believe this should be at the end, please place this under record_errors
There was a problem hiding this comment.
no it does not need to.
Zend/zend.c
Outdated
| EG(errors)[EG(num_errors)-1] = info; | ||
| EG(errors->size)++; | ||
| if (EG(errors->size) >= EG(errors->capacity)) { | ||
| // not sure we can get high number of errors so safe `might be` over cautious here |
Zend/zend.c
Outdated
|
|
||
| static void zend_init_errors_buf(zend_executor_globals *eg) | ||
| { | ||
| eg->errors = pemalloc(ZEND_ERR_BUF_SIZE(2), true); |
There was a problem hiding this comment.
well it needs to live long enough. I m not yet that familiar with Zend engine (why I m doing this PR) though. I ll try in non persistent mode just in case..
Yes this is the reason, remember we re dealing with a "flexible" array now. |
ecc36fc to
3ac9616
Compare
|
ping :) |
arnaud-lb
left a comment
There was a problem hiding this comment.
This is a good idea.
I've added some comments. I also noticed that Nora's comments weren't addressed yet (factor of 1.5, EG(errors)->size instead of EG(errors->size)).
Zend/zend_globals.h
Outdated
| bool record_errors; | ||
| uint32_t num_errors; | ||
| zend_error_info **errors; | ||
| struct zend_err_buf *errors; |
There was a problem hiding this comment.
Suggestion:
| struct zend_err_buf *errors; | |
| struct zend_err_buf errors; |
And change zend_error_info *buf[1] to zend_error_info **errors in struct zend_err_buf.
This would simplify the realloc logic (we only need to reallocate EG(errors).errors), as well as initialization (EG(errors) can be zeroed).
Zend/zend.c
Outdated
| #define COMPILED_STRING_DESCRIPTION_FORMAT "%s(%u) : %s" | ||
|
|
||
| ZEND_API char *zend_make_compiled_string_description(const char *name) /* {{{ */ | ||
| { | ||
| const char *cur_filename; | ||
| int cur_lineno; | ||
| uint32_t cur_lineno; |
There was a problem hiding this comment.
Please avoid unrelated changes
Replace separate num_errors/errors fields with a single struct containing size, capacity, and a flexible array. The buffer grows by 50% when needed instead of reallocating on every recorded error. - Add ZEND_ERR_BUF_SIZE macro and zend_init_errors_buf() helper - Allocate error buffer in zend_startup for NTS builds - Remove redundant NULL checks since buffer is always allocated
3ac9616 to
890f548
Compare
note factor of 1.5 was already addressed for a while EG(errors).capacity + (EG(errors).capacity >> 1 |
890f548 to
480d331
Compare
- Change EG(errors) from pointer to embedded struct (arnaud-lb) - Replace flexible array member with plain pointer (arnaud-lb) - Use EG(errors).field syntax instead of EG(errors->field) (ndossche) - Remove persistent allocation, use erealloc for the inner array - Remove zend_init_errors_buf/ZEND_ERR_BUF_SIZE, zeroing suffices - Free EG(errors).errors buffer in zend_free_recorded_errors - Save/restore full EG(errors) state in error handler paths - Revert unrelated int->uint32_t change (arnaud-lb)
480d331 to
49c8d34
Compare
Zend/zend.c
Outdated
| } | ||
| /* }}} */ | ||
|
|
||
|
|
There was a problem hiding this comment.
Unexpected white space change
Zend/zend.c
Outdated
| uint32_t orig_num_errors = 0; | ||
| uint32_t orig_cap_errors = 0; | ||
| zend_error_info **orig_errors = NULL; |
There was a problem hiding this comment.
Here it maybe possible to replace these 3 variables by a single zend_err_buf variable
Zend/zend.c
Outdated
| uint32_t num_errors = EG(errors).size; | ||
| uint32_t cap_errors = EG(errors).capacity; | ||
| zend_error_info **errors = EG(errors).errors; |
| if (!EG(num_errors)) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
We could keep this check to avoid the memset() and other checks when there are not errors
- Save/restore EG(errors) with struct copy instead of 3 separate vars - Remove extra blank line left from zend_init_errors_buf removal - Restore early return in zend_free_recorded_errors to skip memset
Replace separate num_errors/errors fields with a single struct containing size, capacity, and a flexible array. The buffer grows by 50% when needed instead of reallocating on every recorded error.