| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- Reviewed: no -->
- <sect1 id="performance.classloading">
- <title>Class Loading</title>
- <para>
- Anyone who ever performs profiling of a Zend Framework application will
- immediately recognize that class loading is relatively expensive in Zend
- Framework. Between the sheer number of class files that need to be
- loaded for many components, to the use of plugins that do not have a 1:1
- relationship between their class name and the file system, the various
- calls to <methodname>include_once()</methodname> and
- <methodname>require_once()</methodname> can be problematic. This chapter intends to provide
- some concrete solutions to these issues.
- </para>
- <sect2 id="performance.classloading.includepath">
- <title>How can I optimize my include_path?</title>
- <para>
- One trivial optimization you can do to increase the speed of class
- loading is to pay careful attention to your include_path. In
- particular, you should do four things: use absolute paths (or paths
- relative to absolute paths), reduce the number of include paths you
- define, have your Zend Framework include_path as early as possible,
- and only include the current directory path at the end of your
- include_path.
- </para>
- <sect3 id="performance.classloading.includepath.abspath">
- <title>Use absolute paths</title>
- <para>
- While this may seem a micro-optimization, the fact is that if
- you don't, you'll get very little benefit from <acronym>PHP</acronym>'s realpath
- cache, and as a result, opcode caching will not perform nearly
- as you may expect.
- </para>
- <para>
- There are two easy ways to ensure this. First, you can hardcode
- the paths in your <filename>php.ini</filename>, <filename>httpd.conf</filename>,
- or <filename>.htaccess</filename>. Second, you
- can use <acronym>PHP</acronym>'s <methodname>realpath()</methodname> function when
- setting your include_path:
- </para>
- <programlisting language="php"><![CDATA[
- $paths = array(
- realpath(dirname(__FILE__) . '/../library'),
- '.',
- );
- set_include_path(implode(PATH_SEPARATOR, $paths);
- ]]></programlisting>
- <para>
- You <emphasis>can</emphasis> use relative paths -- so long as
- they are relative to an absolute path:
- </para>
- <programlisting language="php"><![CDATA[
- define('APPLICATION_PATH', realpath(dirname(__FILE__)));
- $paths = array(
- APPLICATION_PATH . '/../library'),
- '.',
- );
- set_include_path(implode(PATH_SEPARATOR, $paths);
- ]]></programlisting>
- <para>
- However, even so, it's typically a trivial task to simply pass
- the path to <methodname>realpath()</methodname>.
- </para>
- </sect3>
- <sect3 id="performance.classloading.includepath.reduce">
- <title>Reduce the number of include paths you define</title>
- <para>
- Include paths are scanned in the order in which they appear in
- the include_path. Obviously, this means that you'll get a result
- faster if the file is found on the first scan rather than the
- last. Thus, a rather obvious enhancement is to simply reduce the
- number of paths in your include_path to only what you need. Look
- through each include_path you've defined, and determine if you
- actually have any functionality in that path that is used in
- your application; if not, remove it.
- </para>
- <para>
- Another optimization is to combine paths. For instance, Zend
- Framework follows <acronym>PEAR</acronym> naming conventions; thus, if you are
- using <acronym>PEAR</acronym> libraries (or libraries from another framework or
- component library that follows <acronym>PEAR</acronym> CS), try to put all of these
- libraries on the same include_path. This can often be achieved
- by something as simple as symlinking one or more libraries into
- a common directory.
- </para>
- </sect3>
- <sect3 id="performance.classloading.includepath.early">
- <title>Define your Zend Framework include_path as early as possible</title>
- <para>
- Continuing from the previous suggestion, another obvious
- optimization is to define your Zend Framework include_path as
- early as possible in your include_path. In most cases, it should
- be the first path in the list. This ensures that files included
- from Zend Framework are found on the first scan.
- </para>
- </sect3>
- <sect3 id="performance.classloading.includepath.currentdir">
- <title>Define the current directory last, or not at all</title>
- <para>
- Most include_path examples show using the current directory, or
- '.'. This is convenient for ensuring that scripts in the same
- directory as the file requiring them can be loaded. However,
- these same examples typically show this path item as the first
- item in the include_path -- which means that the current
- directory tree is always scanned first. In most cases, with Zend
- Framework applications, this is not desired, and the path may be
- safely pushed to the last item in the list.
- </para>
- <example id="performance.classloading.includepath.example">
- <title>Example: Optimized include_path</title>
- <para>
- Let's put all of these suggestions together. Our assumption will be that you
- are using one or more <acronym>PEAR</acronym> libraries in conjunction with
- Zend Framework -- perhaps the PHPUnit and <classname>Archive_Tar</classname>
- libraries -- and that you occasionally need to include
- files relative to the current file.
- </para>
- <para>
- First, we'll create a library directory in our project. Inside that
- directory, we'll symlink our Zend Framework's <filename>library/Zend</filename>
- directory, as well as the necessary directories from our <acronym>PEAR</acronym>
- installation:
- </para>
- <programlisting language="php"><![CDATA[
- library
- Archive/
- PEAR/
- PHPUnit/
- Zend/
- ]]></programlisting>
- <para>
- This allows us to add our own library code if necessary, while
- keeping shared libraries intact.
- </para>
- <para>
- Next, we'll opt to create our include_path programmatically
- within our <filename>public/index.php</filename> file. This allows us to move
- our code around on the file system, without needing to edit the
- include_path every time.
- </para>
- <para>
- We'll borrow ideas from each of the suggestions above: we'll use
- absolute paths, as determined using <methodname>realpath()</methodname>;
- we'll include Zend Framework's include path early; we've
- already consolidated include_paths; and we'll put the current
- directory as the last path. In fact, we're doing really well
- here -- we're going to end up with only two paths.
- </para>
- <programlisting language="php"><![CDATA[
- $paths = array(
- realpath(dirname(__FILE__) . '/../library'),
- '.'
- );
- set_include_path(implode(PATH_SEPARATOR, $paths));
- ]]></programlisting>
- </example>
- </sect3>
- </sect2>
- <sect2 id="performance.classloading.striprequires">
- <title>How can I eliminate unnecessary require_once statements?</title>
- <para>
- Lazy loading is an optimization technique designed to push the
- expensive operation of loading a class file until the last possible
- moment -- i.e., when instantiating an object of that class, calling
- a static class method, or referencing a class constant or static
- property. <acronym>PHP</acronym> supports this via autoloading, which allows you to
- define one or more callbacks to execute in order to map a class name
- to a file.
- </para>
- <para>
- However, most benefits you may reap from autoloading are negated if
- your library code is still performing <methodname>require_once()</methodname> calls --
- which is precisely the case with Zend Framework. So, the question is: how can
- you eliminate those <methodname>require_once()</methodname> calls in order to maximize
- autoloader performance?
- </para>
- <sect3 id="performance.classloading.striprequires.sed">
- <title>Strip require_once calls with find and sed</title>
- <para>
- An easy way to strip <methodname>require_once()</methodname> calls is to use the
- <acronym>UNIX</acronym> utilities 'find' and 'sed' in conjunction to comment out
- each call. Try executing the following statements (where '%'
- indicates the shell prompt):
- </para>
- <programlisting language="shell"><![CDATA[
- % cd path/to/ZendFramework/library
- % find . -name '*.php' -not -wholename '*/Loader/Autoloader.php' \
- -not -wholename '*/Application.php' -print0 | \
- xargs -0 sed --regexp-extended --in-place 's/(require_once)/\/\/ \1/g'
- ]]></programlisting>
- <para>
- This one-liner (broken into two lines for readability) iterates
- through each <acronym>PHP</acronym> file and tells it to replace each instance of
- 'require_once' with '// require_once', effectively commenting
- out each such statement. (It selectively keeps
- <methodname>require_once()</methodname> calls within
- <classname>Zend_Application</classname> and
- <classname>Zend_Loader_Autoloader</classname>, as these classes will fail without
- them.)
- </para>
- <para>
- This command could be added to an automated build or release
- process trivially, helping boost performance in your production
- application. It should be noted, however, that if you use this
- technique, you <emphasis>must</emphasis> utilize autoloading;
- you can do that from your "<filename>public/index.php</filename>" file with the
- following code:
- </para>
- <programlisting language="php"><![CDATA[
- require_once 'Zend/Loader/Autoloader.php';
- Zend_Loader_Autoloader::getInstance();
- ]]></programlisting>
- </sect3>
- </sect2>
- <sect2 id="performance.classloading.pluginloader">
- <title>How can I speed up plugin loading?</title>
- <para>
- Many components have plugins, which allow you to create your own
- classes to utilize with the component, as well as to override
- existing, standard plugins shipped with Zend Framework. This
- provides important flexibility to the framework, but at a price:
- plugin loading is a fairly expensive task.
- </para>
- <para>
- The plugin loader allows you to register class prefix / path pairs,
- allowing you to specify class files in non-standard paths. Each
- prefix can have multiple paths associated with it.
- Internally, the plugin loader loops through each prefix, and then
- through each path attached to it, testing to see if the file exists
- and is readable on that path. It then loads it, and tests to see
- that the class it is looking for is available. As you might imagine,
- this can lead to many stat calls on the file system.
- </para>
- <para>
- Multiply this by the number of components that use the PluginLoader,
- and you get an idea of the scope of this issue. At the time of this
- writing, the following components made use of the PluginLoader:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <classname>Zend_Controller_Action_HelperBroker</classname>: helpers
- </para>
- </listitem>
- <listitem>
- <para>
- <classname>Zend_Dojo</classname>: view helpers, form elements and decorators
- </para>
- </listitem>
- <listitem>
- <para>
- <classname>Zend_File_Transfer</classname>: adapters
- </para>
- </listitem>
- <listitem>
- <para>
- <classname>Zend_Filter_Inflector</classname>: filters (used by the
- ViewRenderer action helper and <classname>Zend_Layout</classname>)
- </para>
- </listitem>
- <listitem>
- <para>
- <classname>Zend_Filter_Input</classname>: filters and validators
- </para>
- </listitem>
- <listitem>
- <para>
- <classname>Zend_Form</classname>: elements, validators, filters,
- decorators, captcha and file transfer adapters
- </para>
- </listitem>
- <listitem>
- <para>
- <classname>Zend_Paginator</classname>: adapters
- </para>
- </listitem>
- <listitem>
- <para>
- <classname>Zend_View</classname>: helpers, filters
- </para>
- </listitem>
- </itemizedlist>
- <para>
- How can you reduce the number of such calls made?
- </para>
- <sect3
- id="performance.classloading.pluginloader.includefilecache">
- <title>Use the PluginLoader include file cache</title>
- <para>
- Zend Framework 1.7.0 adds an include file cache to the
- PluginLoader. This functionality writes "<methodname>include_once()</methodname>"
- calls to a file, which you can then include in your bootstrap. While this
- introduces extra <methodname>include_once()</methodname> calls to your code, it
- also ensures that the PluginLoader returns as early as possible.
- </para>
- <para>
- The PluginLoader documentation <link
- linkend="zend.loader.pluginloader.performance.example">includes
- a complete example of its use</link>.
- </para>
- </sect3>
- </sect2>
- </sect1>
- <!--
- vim:se ts=4 sw=4 et:
- -->
|