From baa21d6e946271a9ea36921a8fa1f40e02ce8604 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 7 Apr 2026 17:10:33 +0800 Subject: [PATCH] Implement caching for config file parsing to improve performance --- .gitignore | 3 ++ src/StaticPHP/Config/ArtifactConfig.php | 33 +++++++++--- src/StaticPHP/Config/ConfigCache.php | 67 +++++++++++++++++++++++++ src/StaticPHP/Config/PackageConfig.php | 20 +++++--- src/StaticPHP/Registry/Registry.php | 2 +- 5 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 src/StaticPHP/Config/ConfigCache.php diff --git a/.gitignore b/.gitignore index 21bae186..810af82c 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,6 @@ spc.exe # dumped files from StaticPHP v3 /dump-*.json + +# config parse cache +/.spc.cache.php diff --git a/src/StaticPHP/Config/ArtifactConfig.php b/src/StaticPHP/Config/ArtifactConfig.php index e3e59fb5..db0af288 100644 --- a/src/StaticPHP/Config/ArtifactConfig.php +++ b/src/StaticPHP/Config/ArtifactConfig.php @@ -42,13 +42,22 @@ class ArtifactConfig if ($content === false) { throw new WrongUsageException("Failed to read artifact config file: {$file}"); } - $data = match (pathinfo($file, PATHINFO_EXTENSION)) { - 'json' => json_decode($content, true), - 'yml', 'yaml' => Yaml::parse($content), - default => throw new WrongUsageException("Unsupported artifact config file format: {$file}"), - }; - if (!is_array($data)) { - throw new WrongUsageException("Invalid JSON format in artifact config file: {$file}"); + // use cache to skip redundant parsing + $data = ConfigCache::get($content); + if ($data !== null) { + logger()->debug("Config cache hit: {$file}"); + } else { + $data = match (pathinfo($file, PATHINFO_EXTENSION)) { + 'json' => json_decode($content, true), + 'yml', 'yaml' => extension_loaded('yaml') ? yaml_parse($content) : Yaml::parse($content), + default => throw new WrongUsageException("Unsupported artifact config file format: {$file}"), + }; + if (!is_array($data)) { + throw new WrongUsageException("Invalid JSON format in artifact config file: {$file}"); + } + if (is_array($data)) { + ConfigCache::set($content, $data); + } } ConfigValidator::validateAndLintArtifacts(basename($file), $data); foreach ($data as $artifact_name => $config) { @@ -68,6 +77,16 @@ class ArtifactConfig return self::$artifact_configs; } + /** + * Restore artifact configs from cache without re-parsing YAML files. + * + * @internal used by Registry cache layer only + */ + public static function _restoreFromCache(array $configs): void + { + self::$artifact_configs = array_merge(self::$artifact_configs, $configs); + } + /** * Get the configuration for a specific artifact by name. * diff --git a/src/StaticPHP/Config/ConfigCache.php b/src/StaticPHP/Config/ConfigCache.php new file mode 100644 index 00000000..a8187957 --- /dev/null +++ b/src/StaticPHP/Config/ConfigCache.php @@ -0,0 +1,67 @@ +/.spc.cache.php (plain PHP, var_export'd array). + * Written once on shutdown when any new entry was added. + */ +class ConfigCache +{ + private static ?array $cache = null; + + private static bool $dirty = false; + + /** + * Return the cached parsed result for $content, or null on miss. + */ + public static function get(string $content): ?array + { + self::load(); + return self::$cache[$content] ?? null; + } + + /** + * Store a parsed result. Will be persisted to disk on shutdown. + */ + public static function set(string $content, array $data): void + { + self::load(); + self::$cache[$content] = $data; + self::$dirty = true; + } + + /** + * Write cache to disk if anything changed. Called automatically on shutdown. + */ + public static function flush(): void + { + if (!self::$dirty) { + return; + } + file_put_contents(self::cachePath(), ' json_decode($content, true), - 'yml', 'yaml' => Yaml::parse($content), - default => throw new WrongUsageException("Unsupported package config file format: {$file}"), - }; + // judge extension — use cache to skip redundant parsing + $data = ConfigCache::get($content); + if ($data !== null) { + logger()->debug("Config cache hit: {$file}"); + } else { + $data = match (pathinfo($file, PATHINFO_EXTENSION)) { + 'json' => json_decode($content, true), + 'yml', 'yaml' => extension_loaded('yaml') ? yaml_parse($content) : Yaml::parse($content), + default => throw new WrongUsageException("Unsupported package config file format: {$file}"), + }; + if (is_array($data)) { + ConfigCache::set($content, $data); + } + } ConfigValidator::validateAndLintPackages(basename($file), $data); foreach ($data as $pkg_name => $config) { self::$package_configs[$pkg_name] = $config; diff --git a/src/StaticPHP/Registry/Registry.php b/src/StaticPHP/Registry/Registry.php index 4075b70b..8f206c30 100644 --- a/src/StaticPHP/Registry/Registry.php +++ b/src/StaticPHP/Registry/Registry.php @@ -64,7 +64,7 @@ class Registry } $data = match (pathinfo($registry_file, PATHINFO_EXTENSION)) { 'json' => json_decode($yaml, true), - 'yaml', 'yml' => Yaml::parse($yaml), + 'yaml', 'yml' => extension_loaded('yaml') ? yaml_parse($yaml) : Yaml::parse($yaml), default => throw new RegistryException("Unsupported registry file format: {$registry_file}"), }; if (!is_array($data)) {