Skip to content
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

Inheritance cache #6627

Closed
wants to merge 40 commits into from
Closed

Inheritance cache #6627

wants to merge 40 commits into from

Conversation

dstogov
Copy link
Member

@dstogov dstogov commented Jan 20, 2021

No description provided.

@dstogov
Copy link
Member Author

dstogov commented Jan 20, 2021

This is a PoC yet (some variance related tests fail). Inheritance cache should eliminate overhead of copying class data from SHM to process memory, run-time class linking, and then data deallocation. This also may help JIT generating better code for property access.

  • there are some problems with variance support
  • it would be great to eliminate limitation for ZEND_ACC_CONSTANTS_UPDATED and ZEND_ACC_PROPERTY_TYPES_RESOLVED to make all cached classes ZEND_ACC_IMMUTABLE
  • JIT might need additional class guards (not tested yet)

@nikic could you please review this and share your opinion and ideas for improvement.

@dstogov dstogov force-pushed the inheritance_cache branch 3 times, most recently from d75c5a6 to 442fe8f Compare January 25, 2021 09:05
Copy link
Member

@nikic nikic left a comment

Choose a reason for hiding this comment

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

I think this generally makes sense, I will need to look into the details a bit more.

it would be great to eliminate limitation for ZEND_ACC_CONSTANTS_UPDATED and ZEND_ACC_PROPERTY_TYPES_RESOLVED to make all cached classes ZEND_ACC_IMMUTABLE

Any ideas on how to do that? More map_ptr usage?

@@ -1059,7 +1059,15 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *
if ((flags & ZEND_FETCH_CLASS_ALLOW_UNLINKED) ||
((flags & ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED) &&
(ce->ce_flags & ZEND_ACC_NEARLY_LINKED))) {
ce->ce_flags |= ZEND_ACC_HAS_UNLINKED_USES;
if (ce->ce_flags & ZEND_ACC_IMMUTABLE) {
Copy link
Member

Choose a reason for hiding this comment

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

If we need the hashtable for immutable classes, I think it would make sense to always use it rather than having two different mechanisms. It's a rare case, so being optimal is not so important.

Copy link
Member Author

Choose a reason for hiding this comment

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

Immutable class may be lazily loaded later, so the same hash key won't work.

}

if (ZCG(enabled) && accel_startup_ok) {
/* Override inheritance cache callbaks */
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/* Override inheritance cache callbaks */
/* Override inheritance cache callbacks */

@@ -373,6 +376,11 @@ static inheritance_status zend_perform_covariant_class_type_check(
zend_class_entry *proto_scope, zend_type proto_type,
bool register_unresolved) {
bool have_unresolved = 0;

if (CG(current_linking_class)) {
CG(current_linking_class)->ce_flags |= ZEND_ACC_NEEDS_VARIANCE_CHECKS;
Copy link
Member

Choose a reason for hiding this comment

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

This also sets the flag if the

if (zend_string_equals_ci(fe_class_name, proto_class_name)) {
case is hit (exactly the same name), which is very common. Maybe this should be inside the lookup_class function?

Copy link
Member Author

Choose a reason for hiding this comment

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

ZEND_ACC_NEEDS_VARIANCE_CHECKS is completely removed.

@nikic
Copy link
Member

nikic commented Jan 25, 2021

Did you already run any tests on how much difference this makes?

@dstogov
Copy link
Member Author

dstogov commented Jan 26, 2021

Any ideas on how to do that? More map_ptr usage?

Yes, I already solved the typed property limitation and work on constants now.

@dstogov
Copy link
Member Author

dstogov commented Jan 26, 2021

Did you already run any tests on how much difference this makes?

0.5% improvement on Wordpress (according to callgrind). I expect better improvement on heavy OOP apps. I'll test after fixing limitations (typed properties, AST constants, variance checks).

@dstogov dstogov force-pushed the inheritance_cache branch 4 times, most recently from a81856a to 895976c Compare February 3, 2021 20:10
@dstogov dstogov requested a review from nikic February 5, 2021 06:43
@dstogov
Copy link
Member Author

dstogov commented Feb 5, 2021

@nikic I think, the PR is ready to merge. Please, review this and schedule more tests.

@dstogov
Copy link
Member Author

dstogov commented Feb 5, 2021

I checked the performance improvement on https://github.com/phpbenchmarks/symfony (without JIT).

1000 requests takes 8% less time (one worker: REQUEST_METHOD=GET REQUEST_URI=/benchmark/helloworld SCRIPT_FILENAME=/home/dmitry/php/phpbenchmark/symfony/public/index.php REDIRECT_STATUS=200 SERVER_NAME=localhost SERVER_PORT=8080 SERVER_ADDR=127.0.0.1 REMOTE_PORT=40304 REMOTE_ADDR=127.0.0.1 sapi/cgi/php-cgi -d opcache.jit=0 -T 1000 > /dev/null)

For the same 1000 requests Callgrind shows 5% less executed instructions.
There are two main sources of performance improvement:

  • inheritance for each class is done once, so zend_inheritance.c takes 10 times less instructions
  • zend_accel_load_script() takes 3 times less instructions, because all classes are immutable.

Zend/zend.h Outdated Show resolved Hide resolved
Zend/zend_compile.h Outdated Show resolved Hide resolved
===DONE===
--EXPECTF--
Warning: Can't preload class Test with unresolved property types in %s on line %d
--EXPECT--
Copy link
Contributor

Choose a reason for hiding this comment

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

This change in expectation seems weird: is the --FILE-- section mostly ignored and only here for representative purposes? Does the Warning still get emitted in some cases, after this patch?

Copy link
Member Author

Choose a reason for hiding this comment

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

Now opcache may preload classes with unresolved properties without problems.

?>
--FILE--
Unreachable
<?php
class Foo {
Copy link
Contributor

Choose a reason for hiding this comment

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

Related to ext/opcache/tests/preload_unresolved_prop_type.phpt - possibly --FILE-- section unused and only needed for representative purposes?

?>
--FILE--
Unreachable
<?php
Copy link
Contributor

Choose a reason for hiding this comment

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

From a reader's perspective, this test is not really understandable: previously, it was clear that --FILE-- was not being used, but now it is, which makes the change in expected results quite suspicious

Copy link
Member Author

Choose a reason for hiding this comment

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

Previously, opcache couldn't preload classes with unresolved constants. Now this is not a problem. Constants that are not resolved during preloading, may be resolved later at run-time.

@@ -11,5 +11,16 @@ require_once('skipif.inc');
if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
?>
--FILE--
--EXPECTF--
Warning: Can't preload class Test with unresolved initializer for constant C in %s on line %d
<?php
Copy link
Contributor

Choose a reason for hiding this comment

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

Same feedback as in ext/opcache/tests/preload_loadable_classes_2.phpt: would be better to keep --FILE-- as it was, then change only --EXPECTF--

Copy link
Member Author

Choose a reason for hiding this comment

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

We would have test with empty --FILE-- and empty --EXPECT-- . The new code shows, that now Test is preloaded, but resolution of constant "Test::C" failed, because "Foo::BAR" is not defined.

@nikic
Copy link
Member

nikic commented Feb 8, 2021

I triggered some additional tests, with the following new failures:

} zend_type;

typedef struct {
uint32_t num_types;
zend_type types[1];
} zend_type_list;

#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 24
#define _ZEND_TYPE_MASK ((1u << 24) - 1)
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25
Copy link
Member

Choose a reason for hiding this comment

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

The intention here was that new flags are added at the low end, i.e. 1<<19. Types only go to 15, so this should be fine.

break;
}
}
if (entry->dependencies) {
Copy link
Member

Choose a reason for hiding this comment

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

This will try to check dependencies even if the above traits_and_interfaces check failed.

It would be best to extract the body of the while(entry) loop into a separate function so that early return can be used.

ext/opcache/ZendAccelerator.c Outdated Show resolved Hide resolved
SEPARATE_ARRAY(zv);
} else if (Z_TYPE_P(zv) == IS_OBJECT || Z_TYPE_P(zv) == IS_RESOURCE) {
/* Can't cache */
return NULL;
Copy link
Member

Choose a reason for hiding this comment

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

Won't this miss something like array of objects?

entry->dependencies_count = zend_hash_num_elements(dependencies);
entry->dependencies = (zend_class_dependency*)ZCG(mem);
ZEND_HASH_FOREACH_STR_KEY_PTR(dependencies, dep_name, dep_ce) {
entry->dependencies[i].name = dep_name;
Copy link
Member

Choose a reason for hiding this comment

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

Are we guaranteed that dep_name is a permanent string here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Class prototype, parent, and all interfaces and traits are IMMUTABLE. So they may refer only to strings in shared memory. Anyway, I'll add assertion.

Copy link
Member Author

Choose a reason for hiding this comment

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

@nikic all known test failures should be fixed now. Could you please reschedule tests.

I initiated full test run myself.

ext/opcache/ZendAccelerator.c Show resolved Hide resolved
c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
memcpy(c, parent_const, sizeof(zend_class_constant));
parent_const = c;
}
Copy link
Member

Choose a reason for hiding this comment

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

I don't really get why this copy is needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can't update constant value in shared memory, so we copy it into process memory.
If you comment this some tests are going to fail. For example tests/classes/constants_basic_006.phpt

@nikic
Copy link
Member

nikic commented Feb 8, 2021

MacOS build currently fails with:

/Users/runner/work/1/s/ext/opcache/zend_accelerator_util_funcs.c:313:32: error: unused function 'fast_memcpy' [-Werror,-Wunused-function]
static zend_always_inline void fast_memcpy(void *dest, const void *src, size_t size)

@dstogov
Copy link
Member Author

dstogov commented Feb 8, 2021

@nikic all known test failures should be fixed now. Could you please reschedule tests.
The only known problem - possible incorrect handling of "captured" static variables (your comment about arrays of objects).

@codecov-io
Copy link

Codecov Report

Merging #6627 (cf2da7f) into master (042e0d8) will increase coverage by 0.04%.
The diff coverage is 83.31%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #6627      +/-   ##
==========================================
+ Coverage   71.92%   71.96%   +0.04%     
==========================================
  Files         826      838      +12     
  Lines      316227   316737     +510     
==========================================
+ Hits       227439   227940     +501     
- Misses      88788    88797       +9     
Impacted Files Coverage Δ
Zend/zend.c 89.09% <ø> (ø)
Zend/zend_compile.h 100.00% <ø> (ø)
Zend/zend_types.h 100.00% <ø> (ø)
ext/curl/curl_private.h 100.00% <ø> (ø)
ext/opcache/shared_alloc_mmap.c 82.05% <ø> (ø)
ext/standard/crc32.c 100.00% <ø> (ø)
ext/standard/file.c 91.33% <ø> (+0.14%) ⬆️
ext/standard/streamsfuncs.c 88.18% <ø> (+0.21%) ⬆️
ext/zend_test/test_arginfo.h 78.94% <0.00%> (-5.96%) ⬇️
sapi/cli/php_cli_server.c 0.00% <0.00%> (ø)
... and 72 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 882b418...cf2da7f. Read the comment docs.

@dstogov
Copy link
Member Author

dstogov commented Feb 9, 2021

The following table compares Inheritance Cache and Preloading.
It shows time of 1000 requests to symphony_demo app without JIT (see azure/community_job.yml)
php-cgi -T10,1000 -d opcache.jit=0 public/index.php > /dev/null

master inheritance_cache
20.87 sec 18.43 sec
preloding 17.14 sec 17.01 sec

As you can see, Inheritance Cache gives a part of Preloading speedup for free (Inheritance Cache - 12%, Preloading - 18%)

@TysonAndre
Copy link
Contributor

I'm only moderately familiar with parts of opcache, so I'm not able to understand this as well as other reviewers:

What does this opcache code do for cyclic dependencies that would cause a runtime fatal error in php (E.g. trait A uses B, and trait B uses A)? (Same for cyclic dependencies in classes/interfaces, etc)

@dstogov
Copy link
Member Author

dstogov commented Feb 9, 2021

I'm only moderately familiar with parts of opcache, so I'm not able to understand this as well as other reviewers:

What does this opcache code do for cyclic dependencies that would cause a runtime fatal error in php (E.g. trait A uses B, and trait B uses A)? (Same for cyclic dependencies in classes/interfaces, etc)

Inheritance Cache just can't store classes before they are linked by PHP, so PHP is going to fail as usually.

@dstogov
Copy link
Member Author

dstogov commented Feb 9, 2021

Merged as 4b79dba

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.

None yet

6 participants