Skip to content

Commit

Permalink
Minor improvements, bump to next release (#3)
Browse files Browse the repository at this point in the history
* Code improvements + prepend autoloader option

* Updates to the readme, updated benchmark info

* Remove the need to have PHPStan ignore a few things

* Fix readme
BelleNottelling authored Jun 8, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 19c70b9 commit b0f3b48
Showing 5 changed files with 84 additions and 90 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ $loader = new AntCMS\AntLoader(); // Create AntLoader with it's default options.
$loader->addNamespace('', 'somepath', 'psr0'); //Add a path for a PSR-0 autoloader, by providing an empty string it'll search for all classes in this path.
$loader->addNamespace('Example\\Class\\', 'someotherpath'); //Add a path for a PSR-4 autoloader, which will only search in that directory for the "Example\Class" namespace.
$loader->checkClassMap(); // Create a new classmap if it doesn't already exist. If it does, load it now.
$loader->register(); // Register the autoloader within PHP.
$loader->register(); // Register the autoloader within PHP. Optionally pass 'true' to this to prepend AntLoader to the start of the autoloader list. PHP will then use AntLoader first when attempting to load classes.

$loader->resetClassMap(); // Reset the classmap, clearing the existing one out from whatever is the current caching method. Will not regenerate one automatically.
```
@@ -80,10 +80,12 @@ Here are some details about the filesystem caching method:
## Notes

### Performance
While it's not strictly necessary to use the classmap functionality, we strongly recommend doing so for optimal performance. In our tests, we found that using the classmap resulted in significant speed improvements:

- Software RAID 0 SSD Array: 85% faster, reducing the time it took to instance 1000 random classes from 0.0691 seconds to 0.01 seconds.
- Standard HDD: 91% faster, reducing the time it took to instance 1000 random classes from 0.0796 seconds to 0.0072 seconds.
- While it's not strictly necessary to use the classmap functionality, we strongly recommend doing so for optimal performance.
- Testing shows that it reduces the time to find and load `1000` random classes by `95%` (from `0.0699` seconds to `0.0037` seconds)
- Depending on the setup, prepending AntLoader may speed up the performance of your application.
- For example, if you are using composer's autoloader and have a lot of composer packages, that may delay the time it takes to load classes within your application.
- In this example, the classes inside of your application will load slightly faster and classes loaded through composer will be slightly slower.
- Potential improvements are highly dependent on the specific application and enviroment. In most situations, the difference is likely to be very minimal.

So, we encourage you to take advantage of the classmap feature to get the best performance out of your application.

46 changes: 23 additions & 23 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 44 additions & 43 deletions src/AntLoader.php
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ class AntLoader
/**
* Creates a new instance of AntLoader.
*
* @param array<string,mixed> $config (optional) Configuration options for AntLoader.
* @param array{mode?:string,path?:string,key?:string,ttl?:int,stopIfNotFound?:bool} $config (optional) Configuration options for AntLoader.
* Available keys:
* - 'mode': What mode to use for storing the classmap. Can be 'auto', 'filesystem', 'apcu', or 'none'.
* - 'path': Where to save the classmap to. By default, this will be saved to a random temp file.
@@ -54,15 +54,15 @@ public function __construct(array $config = [])
$config = array_merge($defaultConfig, $config);

if (empty($config['key'])) {
$generatedID = 'AntLoader_' . hash('md5', __DIR__);
$generatedID = 'AntLoader_' . hash('md5', __FILE__);
} else {
$generatedID = strval($config['key']); // @phpstan-ignore-line
$generatedID = $config['key'];
}

if (empty($config['path'])) {
$this->classMapPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $generatedID;
} else {
$this->classMapPath = strval($config['path']); // @phpstan-ignore-line
$this->classMapPath = $config['path'];
}

$cacheOptions = [
@@ -81,15 +81,14 @@ public function __construct(array $config = [])
]
];

// @phpstan-ignore-next-line
if (array_key_exists($config['mode'], $cacheOptions)) {
$this->cacheType = intval($cacheOptions[$config['mode']]['type']);
$this->cacheKey = strval($cacheOptions[$config['mode']]['key'] ?? '');
} else {
throw new \Exception("Unsupported cache mode. Please ensure you are specifying 'auto', 'filesystem', 'apcu', or 'none'.");
}

$this->cacheTtl = intval($config['ttl']); // @phpstan-ignore-line
$this->cacheTtl = $config['ttl'];
$this->stopIfNotFound = (bool) $config['stopIfNotFound'];
}

@@ -99,33 +98,32 @@ public function __construct(array $config = [])
*/
public function checkClassMap(): void
{
if ($this->cacheType === self::noCache) {
return;
}

if ($this->cacheType === self::fileCache) {
// If the classmap doesn't yet exist, generate a new one now.
if (!file_exists($this->classMapPath)) {
$classMap = $this->generateMap();
$this->classMap = $classMap->getMap();
$this->saveMap();
switch ($this->cacheType) {
case self::noCache:
return;
}

// Otherwise, load the existing one.
$this->classMap = include $this->classMapPath;
} else {
if (apcu_exists($this->cacheKey)) {
$map = apcu_fetch($this->cacheKey);
if (is_array($map)) {
$this->classMap = $map;
case self::fileCache:
// If the classmap doesn't yet exist, generate a new one now.
if (!file_exists($this->classMapPath)) {
$classMap = $this->generateMap();
$this->classMap = $classMap->getMap();
$this->saveMap();
} else {
// Otherwise, load the existing one.
$this->classMap = include $this->classMapPath;
}
return;
case self::apcuCache:
if (apcu_exists($this->cacheKey)) {
$map = apcu_fetch($this->cacheKey);
if (is_array($map)) {
$this->classMap = $map;
}
} else {
$classMap = $this->generateMap();
$this->classMap = $classMap->getMap();
$this->saveMap();
}
} else {
$classMap = $this->generateMap();
$this->classMap = $classMap->getMap();
$this->saveMap();
return;
}
}
}

@@ -142,15 +140,18 @@ public function resetClassMap(): void
@unlink($this->classMapPath);
break;
}

$this->classMap = [];
}

/**
* Registers the autoloader.
* @param bool $prepend (optional) Set to true to cause the autoloader to be added to the start of the PHP autoloader list.
* This will make AntLoader take priority over other autoloaders.
*/
public function register(): void
public function register(bool $prepend = false): void
{
spl_autoload_register(array($this, 'autoload'));
spl_autoload_register(array($this, 'autoload'), true, $prepend);
}

/**
@@ -203,7 +204,6 @@ public function autoload(string $class): void
return;
}

// If this configuration option is set to 'true', AntLoader will stop searching for a class after checking the classmap.
if ($this->stopIfNotFound) {
return;
}
@@ -275,16 +275,17 @@ private function getFile(string $class, string $path, string $namespace): string
*/
private function saveMap(): void
{
if ($this->cacheType === self::noCache) {
return;
}

if ($this->cacheType === self::fileCache) {
$output = '<?php ' . PHP_EOL;
$output .= 'return ' . var_export($this->classMap, true) . ';';
@file_put_contents($this->classMapPath, $output);
} else {
apcu_store($this->cacheKey, $this->classMap, $this->cacheTtl);
switch ($this->cacheType) {
case self::noCache:
return;
case self::fileCache:
$output = '<?php ' . PHP_EOL;
$output .= 'return ' . var_export($this->classMap, true) . ';';
@file_put_contents($this->classMapPath, $output);
return;
case self::fileCache:
apcu_store($this->cacheKey, $this->classMap, $this->cacheTtl);
return;
}
}

8 changes: 6 additions & 2 deletions tests/LoaderTest.php
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@
$loader = setupLoader();

// Test class loading without class map
removeClassMap();
$loader->resetClassMap();
$start = microtime(true);
foreach ($classes as $class) {
expect(class_exists($class))->toBeTrue();
@@ -44,7 +44,11 @@
// Test class loading with class map
deleteRandomClasses();
$classes = createRandomClasses(1000);

$loader = setupLoader(true);
$loader->resetClassMap(); // Ensure we don't have an old class map
$loader->checkClassMap();

$start = microtime(true);
foreach ($classes as $class) {
expect(class_exists($class))->toBeTrue();
@@ -56,7 +60,7 @@
$withMap = $totalTime / 10;

// Clean up and output result
removeClassMap();
$loader->resetClassMap();
deleteRandomClasses();
expect($withMap)->toBeLessThan($withoutMap);
});
21 changes: 4 additions & 17 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -3,32 +3,19 @@ function setupLoader(bool $cache = false)
{
$pathClasses = __DIR__ . DIRECTORY_SEPARATOR . 'Classes' . DIRECTORY_SEPARATOR;

if ($cache) {
$config = [
'mode' => 'auto',
];
} else {
$config = [
'mode' => 'none',
];
}
$config = [
'mode' => $cache ? 'auto' :'none',
];

$loader = new AntCMS\AntLoader($config);
$loader->addNamespace('', $pathClasses . 'PSR0', 'psr0');
$loader->addNamespace('', $pathClasses . 'PSR4');
$loader->addNamespace('', $pathClasses . 'Random');
$loader->checkClassMap();
$loader->register();
$loader->register(true);
return $loader;
}

function removeClassMap()
{
$loader = new AntCMS\AntLoader();
$loader->resetClassMap();
$loader->unRegister();
}


/**
* Generates any number of random classes. Default is 250 classes, which is probably more than most applications will see. But this is a test to just show that the class map is infact optimizing things.

0 comments on commit b0f3b48

Please sign in to comment.