PHP programs are full of global constants that are defined over and over during the runtime even though their value doesn’t change. In this article, we’re describing a way to tackle this potential performance bottleneck.
Global constants are standard in PHP – they drive the program execution, specify the program parameters, etc. The issue is that they are repeatedly being defined at run time, while their value doesn’t change. Let’s explore an option that greatly improves the constants’ maintainability and overall program performance.
The problem
Let’s take a look at the following code, which uses constants. This is a completely standard approach in PHP programs, which you’ll find in almost all platforms, e.g. WordPress, MediaWiki or Symfony.
<?php
define("DEBUG", true);
define("DS", "/");
define("PATH_INC", "inc" + DS);
/// later in the program:
include PATH_INC . "functions.php";
if (DEBUG) { trace($message); }
The code above defines three constants, and then uses them. There is a whole bunch of ways this can cause headaches:
- constant values are a part of the program’s code, making it hard to maintain
- the constants get defined over and over in every request, causing a small performance overhead
- constants won’t be analyzed well, since they are defined at run time, during the program execution, and the eventual analysis might not be sure whether the code that defines them will actually be executed
- using such constants will involve a dynamic resolution
- using values of constants can’t be evaluated at compile-time and has to be postponed to the run time
Possible approaches
We can define the constants in a separate file all in one place. At the very least, this will greatly enhance the code’s maintainability. Still, the constants will be a part of the source code, which would be good to avoid.
Another option would be to use a declarative way – to define the constants in a JSON file or using the more old school INI file, and then read the constants from there. Many apps we come across go with this approach, which seems much cleaner.
Nonetheless, the constants are actually variables, making it hard for IDEs and compilers to analyze their usage, which slows down the performance a bit.
How the compiler sees it
The compiler is very strict about constants and there’s no room for assumptions here. Constants defined in the code must be defined dynamically (a hash table is involved) and also read dynamically, although reading constants can be greatly optimized by at least remembering the slot in the hash table.
The code above roughly results in the following IL instructions (play around with it on your own at try.peachpie.io):
// parameters to define( context, name, value, case_insensitive)
IL_0006: ldarg.0 // get the current context
IL_0007: ldstr "DEBUG" // name of the constant
IL_000c: ldsfld Pchp.Core.PhpValue::True // constant value = TRUE
IL_0011: ldc.i4.0 // 0 = case sensitive
// call the define() method
IL_0012: call bool Pchp.Library.Constants::define(Context, string, PhpValue, bool)
IL_0017: pop
Reading the constant is a matter of one hashtable lookup:
/* * if (DEBUG) ... */ // get the current contextIL_0006: ldarg.0
// constant nameIL_0007: ldstr "DEBUG"
// variable for index in hashtable, calculated when first used to avoid repetitious hashtable lookupsIL_000c: ldsflda int32 '<Script>'::'<const>DEBUG'
// get the constant value using runtime helper method:IL_0011: call Pchp.Core.Operators::ReadConstant(Context, string, int32&)
// if { ... } IL_001b: brfalse.s IL_003d
The include statement becomes too complex, as well.
Compile time constant
The solution would be to already specify the constants at compile time – at least those constants that are not variable and depend on the build configuration. For this purpose, there is the $(DefineConstants)
MSBuild property. It is a common build property the compiler naturally uses.
Constants specified in this property are defined seamlessly into the PHP program. Moreover, they are recognized by the compiler analysis and may be further optimized.
Specifying the following property within the project file can yield significant performance increases and also lets the constant be defined in a project file, not allowing it to be changed and excluding it from the actual source code.
<DefineConstants>DEBUG;DS=/;PATH_INC="inc/"</DefineConstants>
The result
The compiler synthesizes a static class with the build-time defined constants. This is used for eventual reflection and referencing by other projects, so that the constants will remain there. Also, it is used to populate runtime constants when queried by the PHP library function get_defined_contants()
or defined()
or constant()
.
internal static class <constants> {
public const string PATH_INC = "inc/";
public const string DS = "/";
public const bool DEBUG = true;
}
All the usages in the code can be evaluated at compile time now. More importantly, all the conditions involving the constant will be eliminated in the Release compilation mode.
// No sample here; all the IL instructions were eliminated.
Our sample from the above will be simply optimized out now by the compiler.
Conclusion
Using the compile time DefineConstants property is a great way of getting rid of unnecessary code, optimizing the program’s performance and moving the constants’ definition to the project file. Moreover, constant values can be evaluated by the compiler, eventually allowing for more expression to be evaluated subsequently.
This also prevents some security issues since those constants are really constant compile-time values. They cannot be changed from outside after the compilation.
The most common usage is definitely the DEBUG
constant, which is already defined implicitly for the debug build configuration.