Repository navigation
Conversation
Property reads of declared int/float scalars were lowered to attr(offset, AttrMode::Get), which materializes a Variant on every read and adds refcount traffic plus a temporary that the following conversion has to unwrap. Read the zval slot directly through attrInt/attrFloat instead when the property is stored as a native scalar. The receiver must be an exact class so the persistent property offset cannot drift when a subclass redeclares the property: `$this` in a final class, or a typed object variable whose declared class is final. Readonly properties keep the Zend initialization check, nullable properties may hold null (which the typed accessors reject), and properties with hooks keep the existing lowering, so all of those stay on the Variant path. The probe under probes/typed_property_read measures 20M reads of two declared scalars at 1169ms before and 125ms after on this machine, with identical output; generated code confirms attr(get_persistent_prop(...)) drops to zero and typed offset reads appear instead.
The typed offset read assumed that a proven runtime class also means Object storage. A `mixed` parameter narrowed by instanceof disproves that: the class is known, but the C++ binding is still a Variant, which has no attrInt or attrFloat member, so instanceof-type-narrowing.phpt failed to compile. Require Object storage for non-this receivers before emitting the typed accessor; Variant bindings fall back to attr(offset, AttrMode::Get). Union typed object parameters keep Object storage and still take the fast path. Cover both directions: a PHPT for a narrowed mixed receiver, and a codegen assertion that the fallback read is emitted instead of attrInt.
Contributor
Author
|
接收者还要求 C++ 存储为 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
AOT 编译器把“已声明的
int/float实例属性读取”降级为attr(offset, AttrMode::Get)。即使偏移来自get_persistent_prop()(无哈希查找),该读法仍会为每次读取构造一个Variant,随后立刻被toInt()/toFloat()解包,并带有引用计数与临时变量unset()的开销,热点循环里这类读取占比很高。本 PR 在属性确定为“原生标量存储的已声明
int/float”时,直接读 zval slot。复现
改动前生成(每次迭代都构造并清理 Variant):
tmp_var_0 = row.attr(get_persistent_prop(PersistentPropertyId{0}, get_str(4), get_str(0)), php::AttrMode::Get); // ... tmp_var_0.unset(); tmp_var_1.unset();改动后:
tmp_var_0 = php::toFloat(row.attrFloat(get_persistent_prop(PersistentPropertyId{0}, get_str(4), get_str(0)))); tmp_var_1 = php::toInt(row.attrInt(get_persistent_prop(PersistentPropertyId{1}, get_str(2), get_str(0))));根因
parsePropertyFetch()中,$this之外的 类型化对象变量只有在属性为readonly时才会走emitNativeInstancePropertyTypedFetch()的convertExprFromType()分支;普通(非 readonly)已声明标量属性落回通用路径,生成attr(offset, Get)+ Variant 临时变量。而 phpx 早已提供attrInt()/attrFloat()(OBJ_PROP+unwrap_zval,返回引用,不构造 Variant)。改动
src/Parser/PropertyAccessTrait.php新增emitDeclaredScalarPropertyRead(),在parsePropertyFetch()的原生标量分支之前尝试拦截;命中则设置NATIVE_PROPERTY_VALUE_VAR与ATTR_LOWERED_TYPE,生成attrInt(offset)/attrFloat(offset)。仅在以下条件全部满足时生效:
!$update),属性为public、非static,无 getter/setter、非virtual(无属性 hook)int/float,且usesNativeScalarStorage()为真(排除varInt)attrInt/attrFloat对null抛TypeError$this,或声明类型为 final 的类型化对象变量——避免子类重声明属性导致持久化偏移漂移#[Native]类非 final 接收者、readonly、nullable、带 hook 的属性全部保持原有生成路径不变。
为什么只覆盖 int / float
这是“能证明与原路径逐位等价”的最小集合,而不是类型支持的终点:
Object::attrInt()/attrFloat()(include/phpx.h:2677/:2688)返回Int&/Float&,直接映射 zval 的IS_LONG/IS_DOUBLE存储布局,因此可以只读地读取 slot 本身,不构造Variant。当前头文件里不存在attrBool/attrStr/attrArray。attrInt。php::Bool是typedef bool(include/phpx_types.h:38,1 字节),而属性槽是 zval(IS_TRUE/IS_FALSE)。attrInt()在Z_TYPE_P(value) != IS_LONG时会抛TypeError,所以 bool 必须新增attrBool,且由于 C++bool与 zval 布局不同,只能返回值或代理对象,无法像Int&/Float&那样返回引用。生成端目前仍是php::toBool(this_.attr(...))/php::toBool($obj.attr(offset, Get)),emitNativeInstancePropertyTypedFetch()的Type::BOOL分支也明确走convertBoolExpr()。class String : public Variant(include/phpx.h:1846),读字符串天生就是持有/复制一个 zval;即使新增 accessor 也无法消除这层表示,收益远小于 int/float,却要先处理 refcount 生命周期(临时值销毁、返回引用、写入别名),语义风险不对称。nativeStorageType的独立存储,根本不在对象 zval slot 上,需要单独的读写路径。toFloat、326 处紧跟toInt;3,773 处attr(offset, Get)也以数值属性为主。先落地类型可判定、存储布局一一对应的 int/float,才能在不引入新 phpx API、不改运行时语义的前提下拿到主要收益;bool/string 的扩展顺序是 phpx 先提供Bool &attrBool,再评估Str &attrStr。readonly、nullable、带 hook 属性不在本节讨论范围内——它们是属性语义限制(初始化检查、null、属性访问器),与类型无关。
测试
tests/compiler/object_property/)4 个:declared-scalar-read-final-class.phpt:final 类,类型化参数循环 +$this方法declared-scalar-read-default-initialization.phpt:无默认值读作0/0.0declared-scalar-read-nullable.phpt:?int的null/42/nulldeclared-scalar-read-subclass-redeclare.phpt:非 final 基类 + 子类重声明,保证偏移不回退为错误读取phpunit/src/DeclaredScalarPropertyReadTest.php+ 4 个phpunit/code/fixture:attrInt(get_persistent_prop(/attrFloat(get_persistent_prop(/this_.attrInt(/this_.attrFloat(attr(get_persistent_prop(,readonly 仍生成typephp_read_property_scoped(验证
tests/compiler/object_property全量 74/74 PASS(含 readonly / hook / native property 用例);tests/compiler/{switch,devirtualize}31/31 PASSOK (4 tests, 12 assertions);全量 PHPUnit 3640 tests,仅有 7 个既有/环境性问题(5 个CompilationStatisticsTest缺swoole/php-nano、2 个 clang codegen 缺 phpxVarList/PHPX_CATCH符号),已在 master 上复现同样失败probes/typed_property_read,20M 次读取,-O2,best-of-3):1169ms → 125ms(约 9.4x),输出逐字节一致sum=170000000;生成代码中attr(get_persistent_prop(2 → 0,attrInt(/attrFloat(各 0 → 1$ref = &$box->count; $ref = 9;仍指向同一 slot(attrInt(...).toReference());null接收者与 master 一样抛Error: Attempt to read property on nullphp -l、php-cs-fixer --dry-run(改动部分无新增偏差)、phpstan(新增代码无新增报错)通过风险与兼容性
int/float、非 readonly、非 nullable、无 hook、final 精确接收者”的读路径;其余生成代码不变int/float(且非varInt)。bool/string/array/object 仍走Variant路径,需 phpx 新增对应 accessor 后再扩展;本 PR 不引入 phpx 变更,也不改变这些类型的运行时语义