Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor improvements, bump to next release #3

Merged
merged 4 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
```
Expand Down Expand Up @@ -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.

Expand Down
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
Expand Up @@ -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.
Expand All @@ -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 = [
Expand All @@ -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'];
}

Expand All @@ -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;
}
}
}

Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
}

Expand Down
8 changes: 6 additions & 2 deletions tests/LoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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
Expand Up @@ -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.
Expand Down