performance-classloading.xml 15 KB

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