2
0

performance-classloading.xml 14 KB

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