2
0

performance-classloading.xml 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- Reviewed: no -->
  3. <sect1 id="performance.classloading">
  4. <title>Class Loading</title>
  5. <para>
  6. Anyone who ever performs profiling of a Zend Framework application will
  7. immediately recognize that class loading is relatively expensive in Zend
  8. Framework. Between the sheer number of class files that need to be
  9. loaded for many components, to the use of plugins that do not have a 1:1
  10. relationship between their class name and the file system, the various
  11. calls to <methodname>include_once</methodname> and <methodname>require_once</methodname>
  12. can be problematic. This chapter intends to provide some concrete solutions to
  13. these issues.
  14. </para>
  15. <sect2 id="performance.classloading.includepath">
  16. <title>How can I optimize my include_path?</title>
  17. <para>
  18. One trivial optimization you can do to increase the speed of class
  19. loading is to pay careful attention to your include_path. In
  20. particular, you should do four things: use absolute paths (or paths
  21. relative to absolute paths), reduce the number of include paths you
  22. define, have your Zend Framework include_path as early as possible,
  23. and only include the current directory path at the end of your
  24. include_path.
  25. </para>
  26. <sect3 id="performance.classloading.includepath.abspath">
  27. <title>Use absolute paths</title>
  28. <para>
  29. While this may seem a micro-optimization, the fact is that if
  30. you don't, you'll get very little benefit from PHP's realpath
  31. cache, and as a result, opcode caching will not perform nearly
  32. as you may expect.
  33. </para>
  34. <para>
  35. There are two easy ways to ensure this. First, you can hardcode
  36. the paths in your php.ini, httpd.conf, or .htaccess. Second, you
  37. can use PHP's <methodname>realpath()</methodname> function when setting your
  38. include_path:
  39. </para>
  40. <programlisting language="php"><![CDATA[
  41. $paths = array(
  42. realpath(dirname(__FILE__) . '/../library'),
  43. '.',
  44. );
  45. set_include_path(implode(PATH_SEPARATOR, $paths);
  46. ]]></programlisting>
  47. <para>
  48. You <emphasis>can</emphasis> use relative paths -- so long as
  49. they are relative to an absolute path:
  50. </para>
  51. <programlisting language="php"><![CDATA[
  52. define('APPLICATION_PATH', realpath(dirname(__FILE__)));
  53. $paths = array(
  54. APPLICATION_PATH . '/../library'),
  55. '.',
  56. );
  57. set_include_path(implode(PATH_SEPARATOR, $paths);
  58. ]]></programlisting>
  59. <para>
  60. However, even so, it's typically a trivial task to simply pass
  61. the path to <methodname>realpath()</methodname>.
  62. </para>
  63. </sect3>
  64. <sect3 id="performance.classloading.includepath.reduce">
  65. <title>Reduce the number of include paths you define</title>
  66. <para>
  67. Include paths are scanned in the order in which they appear in
  68. the include_path. Obviously, this means that you'll get a result
  69. faster if the file is found on the first scan rather than the
  70. last. Thus, a rather obvious enhancement is to simply reduce the
  71. number of paths in your include_path to only what you need. Look
  72. through each include_path you've defined, and determine if you
  73. actually have any functionality in that path that is used in
  74. your application; if not, remove it.
  75. </para>
  76. <para>
  77. Another optimization is to combine paths. For instance, Zend
  78. Framework follows PEAR naming conventions; thus, if you are
  79. using PEAR libraries (or libraries from another framework or
  80. component library that follows PEAR CS), try to put all of these
  81. libraries on the same include_path. This can often be achieved
  82. by something as simple as symlinking one or more libraries into
  83. a common directory.
  84. </para>
  85. </sect3>
  86. <sect3 id="performance.classloading.includepath.early">
  87. <title>Define your Zend Framework include_path as early as possible</title>
  88. <para>
  89. Continuing from the previous suggestion, another obvious
  90. optimization is to define your Zend Framework include_path as
  91. early as possible in your include_path. In most cases, it should
  92. be the first path in the list. This ensures that files included
  93. from Zend Framework are found on the first scan.
  94. </para>
  95. </sect3>
  96. <sect3 id="performance.classloading.includepath.currentdir">
  97. <title>Define the current directory last, or not at all</title>
  98. <para>
  99. Most include_path examples show using the current directory, or
  100. '.'. This is convenient for ensuring that scripts in the same
  101. directory as the file requiring them can be loaded. However,
  102. these same examples typically show this path item as the first
  103. item in the include_path -- which means that the current
  104. directory tree is always scanned first. In most cases, with Zend
  105. Framework applications, this is not desired, and the path may be
  106. safely pushed to the last item in the list.
  107. </para>
  108. <example id="performance.classloading.includepath.example">
  109. <title>Example: Optimized include_path</title>
  110. <para>
  111. Let's put all of these suggestions together. Our assumption will
  112. be that you are using one or more PEAR libraries in conjunction
  113. with Zend Framework -- perhaps the PHPUnit and Archive_Tar
  114. libraries -- and that you occasionally need to include
  115. files relative to the current file.
  116. </para>
  117. <para>
  118. First, we'll create a library directory in our project. Inside
  119. that directory, we'll symlink our Zend Framework's <filename>library/Zend</filename>
  120. directory, as well as the necessary directories from our PEAR
  121. installation:
  122. </para>
  123. <programlisting language="php"><![CDATA[
  124. library
  125. Archive/
  126. PEAR/
  127. PHPUnit/
  128. Zend/
  129. ]]></programlisting>
  130. <para>
  131. This allows us to add our own library code if necessary, while
  132. keeping shared libraries intact.
  133. </para>
  134. <para>
  135. Next, we'll opt to create our include_path programmatically
  136. within our <filename>public/index.php</filename> file. This allows us to move our
  137. code around on the file system, without needing to edit the
  138. include_path every time.
  139. </para>
  140. <para>
  141. We'll borrow ideas from each of the suggestions above: we'll use
  142. absolute paths, as determined using <methodname>realpath()</methodname>;
  143. we'll include the Zend Framework include path early; we've
  144. already consolidated include_paths; and we'll put the current
  145. directory as the last path. In fact, we're doing really well
  146. here -- we're going to end up with only two paths.
  147. </para>
  148. <programlisting language="php"><![CDATA[
  149. $paths = array(
  150. realpath(dirname(__FILE__) . '/../library'),
  151. '.'
  152. );
  153. set_include_path(implode(PATH_SEPARATOR, $paths));
  154. ]]></programlisting>
  155. </example>
  156. </sect3>
  157. </sect2>
  158. <sect2 id="performance.classloading.striprequires">
  159. <title>How can I eliminate unnecessary require_once statements?</title>
  160. <para>
  161. Lazy loading is an optimization technique designed to push the
  162. expensive operation of loading a class file until the last possible
  163. moment -- i.e., when instantiating an object of that class, calling
  164. a static class method, or referencing a class constant or static
  165. property. PHP supports this via autoloading, which allows you to
  166. define one or more callbacks to execute in order to map a class name
  167. to a file.
  168. </para>
  169. <para>
  170. However, most benefits you may reap from autoloading are negated if
  171. your library code is still performing require_once calls -- which is
  172. precisely the case with Zend Framework. So, the question is: how can
  173. you eliminate those require_once calls in order to maximize
  174. autoloader performance?
  175. </para>
  176. <sect3 id="performance.classloading.striprequires.sed">
  177. <title>Strip require_once calls with find and sed</title>
  178. <para>
  179. An easy way to strip require_once calls is to use the UNIX
  180. utilities 'find' and 'sed' in conjunction to comment out each
  181. call. Try executing the following statements (where '%'
  182. indicates the shell prompt):
  183. </para>
  184. <programlisting language="shell"><![CDATA[
  185. % cd path/to/ZendFramework/library
  186. % find . -name '*.php' -not -wholename '*/Loader/Autoloader.php' -print0 | \
  187. xargs -0 sed --regexp-extended --in-place 's/(require_once)/\/\/ \1/g'
  188. ]]></programlisting>
  189. <para>
  190. This one-liner (broken into two lines for readability) iterates
  191. through each PHP file and tells it to replace each instance of
  192. 'require_once' with '// require_once', effectively commenting
  193. out each such statement.
  194. </para>
  195. <para>
  196. This command could be added to an automated build or release
  197. process trivially, helping boost performance in your production
  198. application. It should be noted, however, that if you use this
  199. technique, you <emphasis>must</emphasis> utilize autoloading;
  200. you can do that from your "<filename>public/index.php</filename>" file with the
  201. following code:
  202. </para>
  203. <programlisting language="php"><![CDATA[
  204. require_once 'Zend/Loader/Autoloader.php';
  205. Zend_Loader_Autoloader::getInstance();
  206. ]]></programlisting>
  207. </sect3>
  208. </sect2>
  209. <sect2 id="performance.classloading.pluginloader">
  210. <title>How can I speed up plugin loading?</title>
  211. <para>
  212. Many components have plugins, which allow you to create your own
  213. classes to utilize with the component, as well as to override
  214. existing, standard plugins shipped with Zend Framework. This
  215. provides important flexibility to the framework, but at a price:
  216. plugin loading is a fairly expensive task.
  217. </para>
  218. <para>
  219. The plugin loader allows you to register class prefix / path pairs,
  220. allowing you to specify class files in non-standard paths. Each
  221. prefix can have multiple paths associated with it.
  222. Internally, the plugin loader loops through each prefix, and then
  223. through each path attached to it, testing to see if the file exists
  224. and is readable on that path. It then loads it, and tests to see
  225. that the class it is looking for is available. As you might imagine,
  226. this can lead to many stat calls on the file system.
  227. </para>
  228. <para>
  229. Multiply this by the number of components that use the PluginLoader,
  230. and you get an idea of the scope of this issue. At the time of this
  231. writing, the following components made use of the PluginLoader:
  232. </para>
  233. <itemizedlist>
  234. <listitem><para>
  235. <classname>Zend_Controller_Action_HelperBroker</classname>: helpers
  236. </para></listitem>
  237. <listitem><para>
  238. <classname>Zend_Dojo</classname>: view helpers, form elements and decorators
  239. </para></listitem>
  240. <listitem><para>
  241. <classname>Zend_File_Transfer</classname>: adapters
  242. </para></listitem>
  243. <listitem><para>
  244. <classname>Zend_Filter_Inflector</classname>: filters (used by the
  245. ViewRenderer action helper and <classname>Zend_Layout</classname>)
  246. </para></listitem>
  247. <listitem><para>
  248. <classname>Zend_Filter_Input</classname>: filters and validators
  249. </para></listitem>
  250. <listitem><para>
  251. <classname>Zend_Form</classname>: elements, validators, filters,
  252. decorators, captcha and file transfer adapters
  253. </para></listitem>
  254. <listitem><para>
  255. <classname>Zend_Paginator</classname>: adapters
  256. </para></listitem>
  257. <listitem><para>
  258. <classname>Zend_View</classname>: helpers, filters
  259. </para></listitem>
  260. </itemizedlist>
  261. <para>
  262. How can you reduce the number of such calls made?
  263. </para>
  264. <sect3
  265. id="performance.classloading.pluginloader.includefilecache">
  266. <title>Use the PluginLoader include file cache</title>
  267. <para>
  268. Zend Framework 1.7.0 adds an include file cache to the
  269. PluginLoader. This functionality writes "include_once" calls to
  270. a file, which you can then include in your bootstrap. While this
  271. introduces extra include_once calls to your code, it also
  272. ensures that the PluginLoader returns as early as possible.
  273. </para>
  274. <para>
  275. The PluginLoader documentation <link
  276. linkend="zend.loader.pluginloader.performance.example">includes
  277. a complete example of its use</link>.
  278. </para>
  279. </sect3>
  280. </sect2>
  281. </sect1>
  282. <!--
  283. vim:se ts=4 sw=4 et:
  284. -->