{"action":"created","issue":{"url":"https://api.github.com/repos/yiisoft/yii2/issues/8189","labels_url":"https://api.github.com/repos/yiisoft/yii2/issues/8189/labels{/name}","comments_url":"https://api.github.com/repos/yiisoft/yii2/issues/8189/comments","events_url":"https://api.github.com/repos/yiisoft/yii2/issues/8189/events","html_url":"https://github.com/yiisoft/yii2/pull/8189","id":70962599,"number":8189,"title":"Improve performance by using static variables for class-level data","user":{"login":"laszlovl","id":12112486,"avatar_url":"https://avatars.githubusercontent.com/u/12112486?v=3","gravatar_id":"","url":"https://api.github.com/users/laszlovl","html_url":"https://github.com/laszlovl","followers_url":"https://api.github.com/users/laszlovl/followers","following_url":"https://api.github.com/users/laszlovl/following{/other_user}","gists_url":"https://api.github.com/users/laszlovl/gists{/gist_id}","starred_url":"https://api.github.com/users/laszlovl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/laszlovl/subscriptions","organizations_url":"https://api.github.com/users/laszlovl/orgs","repos_url":"https://api.github.com/users/laszlovl/repos","events_url":"https://api.github.com/users/laszlovl/events{/privacy}","received_events_url":"https://api.github.com/users/laszlovl/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/yiisoft/yii2/labels/status:under%20discussion","name":"status:under discussion","color":"f7c6c7"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2015-04-25T20:20:10Z","updated_at":"2015-04-27T18:11:05Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/yiisoft/yii2/pulls/8189","html_url":"https://github.com/yiisoft/yii2/pull/8189","diff_url":"https://github.com/yiisoft/yii2/pull/8189.diff","patch_url":"https://github.com/yiisoft/yii2/pull/8189.patch"},"body":"While profiling a Yii2 application, I discovered some interesting results: a few (yii-internal) functions that were called a very large amount of times, while they were essentially calculating static data.\r\n\r\nA good example is BaseActiveRecord's __get() function: almost every invocation results in a call to BaseActiveRecord.hasAttribute(), which leads to Model.attributes(), which uses reflection on the object's class to determine its set of properties.\r\n\r\nRight now, the entire process of reflection is re-calculated (almost) every time __get() is called on an object, while the return value of all these intermediary functions (hasAttribute(), attributes()) could be cached not only within an object instance, but within an object's class: while not all of the affected functions are static*, for all of them it's impossible that invocations on two different instances of the same class would ever return different results.\r\n\r\nPHP's \"static variables\" (http://php.net/manual/en/language.variables.scope.php) are perfect for this situation: it remembers the contents of a variable in a function's scope across all invocations to that function within the same class.\r\n\r\nWith this commit, this strategy is added to all applicable functions that are likely to be called many times within the same request. The performance gains can be huge, especially in situations where many instances of the same ActiveRecord are instanciated in a single request. These are the profiling statistics for a real-world application where around 1350 ActiveRecord instances - spread over mostly 4 different classes - are used on a single page:\r\n\r\nBefore:\r\n```\r\nyii\\db\\Connection::getSchema \t10,229 calls\r\n\r\nTotal Incl. Wall Time (microsec):\t1,704,351 microsecs\r\nTotal Incl. CPU (microsecs):\t1,688,537 microsecs\r\nTotal Incl. MemUse (bytes):\t22,212,712 bytes\r\nTotal Incl. PeakMemUse (bytes):\t23,626,296 bytes\r\nNumber of Function Calls:\t\t245,465\r\n```\r\n\r\nAfter:\r\n```\r\nyii\\db\\Connection::getSchema \t28 calls\r\n\r\nTotal Incl. Wall Time (microsec):\t1,205,574 microsecs\r\nTotal Incl. CPU (microsecs):\t1,198,505 microsecs\r\nTotal Incl. MemUse (bytes):\t22,247,936 bytes\r\nTotal Incl. PeakMemUse (bytes):\t23,598,200 bytes\r\nNumber of Function Calls:\t168,656\r\n```\r\n\r\n.. an improvement in execution time of **30%**.\r\n\r\n*We could later refactor these functions to become static, by using `new ReflectionClass(get_called_class())` instead of `new ReflectionClass($this)`."},"comment":{"url":"https://api.github.com/repos/yiisoft/yii2/issues/comments/96763484","html_url":"https://github.com/yiisoft/yii2/pull/8189#issuecomment-96763484","issue_url":"https://api.github.com/repos/yiisoft/yii2/issues/8189","id":96763484,"user":{"login":"laszlovl","id":12112486,"avatar_url":"https://avatars.githubusercontent.com/u/12112486?v=3","gravatar_id":"","url":"https://api.github.com/users/laszlovl","html_url":"https://github.com/laszlovl","followers_url":"https://api.github.com/users/laszlovl/followers","following_url":"https://api.github.com/users/laszlovl/following{/other_user}","gists_url":"https://api.github.com/users/laszlovl/gists{/gist_id}","starred_url":"https://api.github.com/users/laszlovl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/laszlovl/subscriptions","organizations_url":"https://api.github.com/users/laszlovl/orgs","repos_url":"https://api.github.com/users/laszlovl/repos","events_url":"https://api.github.com/users/laszlovl/events{/privacy}","received_events_url":"https://api.github.com/users/laszlovl/received_events","type":"User","site_admin":false},"created_at":"2015-04-27T18:11:05Z","updated_at":"2015-04-27T18:11:05Z","body":"I found a few more which have can a huge impact during (bulk) updates: for example `Model::scenarios()` could be safely cached amongst all instances of a class in most codebases, as long as the logic in all of your `rules()` is static and doesn't reference `$this`.\r\n\r\nPerhaps I should create a separate extension with a \"faster but less flexible\" ActiveRecord that includes all these performance tweaks and carefully explain the caveats."}}