-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Performance improvements #3808
Performance improvements #3808
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@@ -20,6 +21,66 @@ namespace AppInstaller::Regex | |||
using uregex_ptr = wil::unique_any<URegularExpression*, decltype(uregex_close), uregex_close>; | |||
using utext_ptr = wil::unique_any<UText*, decltype(utext_close), utext_close>; | |||
|
|||
static std::unique_ptr<impl> Create(std::string_view pattern, Options options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can you add a comment stating this does caching to avoid recompiling the regex, please?
src/AppInstallerCommonCore/Regex.cpp
Outdated
auto itr = s_regex_cache.map.find(requested); | ||
if (itr != s_regex_cache.map.end()) | ||
{ | ||
return std::make_unique<impl>(itr->second); | ||
} | ||
} | ||
|
||
auto exclusiveLock = s_regex_cache.lock.lock_exclusive(); | ||
|
||
auto itr = s_regex_cache.map.find(requested); | ||
if (itr != s_regex_cache.map.end()) | ||
{ | ||
return std::make_unique<impl>(itr->second); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: please add a comment about why we need to search again with the exclusive lock. or anything else to not have to figure out the concurrency stuff when reading the code
static std::optional<typename Table::id_t> GetIdById(const SQLite::Connection& connection, SQLite::rowid_t id) | ||
{ | ||
auto statement = details::ManifestTableGetIdsById_Statement(connection, id, { details::GetManifestTableColumnName<Table>() }, false); | ||
if (statement.Step()) { return statement.GetColumn<typename Table::id_t>(0); } else { return std::nullopt; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: linebreaks
@@ -40,6 +40,11 @@ namespace AppInstaller::SQLite | |||
return Utility::ConvertUnixEpochToSystemClock(lastWriteTime); | |||
} | |||
|
|||
std::string SQLiteStorageBase::GetDatabaseIdentifier() | |||
{ | |||
return MetadataTable::GetNamedValue<std::string>(m_dbconn, s_MetadataValueName_DatabaseIdentifier); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this return empty when not set?
// Creating the source reference for this is sufficient to cause the cache to be updated on next Open. | ||
InstalledForceCacheUpdate, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me if this is an actual source I could use, or if it is just to force the update. Maybe update the comment to "Same as Installed, but creating the source reference causes the cache to be updated on the next Open"
Edit: It was clear once I saw the usage, but I'd still appreciate a clarification here
var installedCatalogReference = this.packageManager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages); | ||
|
||
// Ensure package is not installed | ||
string targetPackageProductCode = "{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I had to look up what this GUID was. Can this be a constant somewhere else?
src/AppInstallerCLITests/TestHooks.h
Outdated
@@ -39,6 +39,9 @@ namespace AppInstaller | |||
namespace Repository::Microsoft | |||
{ | |||
void TestHook_SetPinningIndex_Override(std::optional<std::filesystem::path>&& indexPath); | |||
|
|||
using GetARPKeyFunc = Registry::Key(*)(void*, Manifest::ScopeEnum, Utility::Architecture); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Could we use std::function
here?
auto result1 = source1->Search({}); | ||
REQUIRE(!result1.Matches.empty()); | ||
|
||
for (const auto& match : result1.Matches) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Could you add a comment about what this is loop is doing?
auto result1 = source1->Search({}); | ||
REQUIRE(!result1.Matches.empty()); | ||
|
||
for (const auto& match : result1.Matches) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could/should we check that there are no packages in result2 that aren't in result1?
Architecture architectureCallback = Architecture::Unknown; | ||
|
||
ScopeEnum scopeTarget = ScopeEnum::User; | ||
Architecture architectureTarget = Architecture::X64; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this test run on x86?
azure-pipelines.yml
Outdated
# - task: VSTest@2 | ||
# displayName: 'Run tests: Microsoft.Management.Configuration.UnitTests (InProc)' | ||
# inputs: | ||
# testRunTitle: Microsoft.Management.Configuration.UnitTests (InProc) | ||
# testSelector: 'testAssemblies' | ||
# testAssemblyVer2: '**\Microsoft.Management.Configuration.UnitTests.dll' | ||
# searchFolder: '$(buildOutDir)\Microsoft.Management.Configuration.UnitTests' | ||
# codeCoverageEnabled: true | ||
# platform: '$(buildPlatform)' | ||
# configuration: '$(BuildConfiguration)' | ||
# diagnosticsEnabled: true | ||
# condition: succeededOrFailed() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use enabled: false
instead of commenting it out
# - task: VSTest@2 | |
# displayName: 'Run tests: Microsoft.Management.Configuration.UnitTests (InProc)' | |
# inputs: | |
# testRunTitle: Microsoft.Management.Configuration.UnitTests (InProc) | |
# testSelector: 'testAssemblies' | |
# testAssemblyVer2: '**\Microsoft.Management.Configuration.UnitTests.dll' | |
# searchFolder: '$(buildOutDir)\Microsoft.Management.Configuration.UnitTests' | |
# codeCoverageEnabled: true | |
# platform: '$(buildPlatform)' | |
# configuration: '$(BuildConfiguration)' | |
# diagnosticsEnabled: true | |
# condition: succeededOrFailed() | |
- task: VSTest@2 | |
displayName: 'Run tests: Microsoft.Management.Configuration.UnitTests (InProc)' | |
inputs: | |
testRunTitle: Microsoft.Management.Configuration.UnitTests (InProc) | |
testSelector: 'testAssemblies' | |
testAssemblyVer2: '**\Microsoft.Management.Configuration.UnitTests.dll' | |
searchFolder: '$(buildOutDir)\Microsoft.Management.Configuration.UnitTests' | |
codeCoverageEnabled: true | |
platform: '$(buildPlatform)' | |
configuration: '$(BuildConfiguration)' | |
diagnosticsEnabled: true | |
enabled: false | |
condition: succeededOrFailed() | |
This comment has been minimized.
This comment has been minimized.
@@ -25,6 +25,9 @@ namespace AppInstaller::SQLite | |||
// Gets the last write time for the database. | |||
std::chrono::system_clock::time_point GetLastWriteTime(); | |||
|
|||
// Gets the identifier written to the database when it was created. | |||
std::string GetDatabaseIdentifier(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this method used only for testing purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it exclusively for test purposes at this time. But it seemed like an interesting enough idea to just leave it in for all databases.
Possible trigger for debugging
The log file in |
Cherry-pick of #3808 , then fixing up the changes (mostly namespace changes, but also bring one extra function over) for the fact that the SQLite base code moved around.
Change
This change has several performance improvements based on profiling.
Installed package caching
A global process cache is stored for the unfiltered install data. It is very basic for updates right now to make it more reasonable to service, but future changes could improve things to examine the system data and only do delta updates. It could also be saved to disk to improve the performance of future processes.
The cache itself is not used by any callers, but instead in-memory copies are handed out. The cache sets up event listeners for the current data sources and waits for these events before it will attempt to update again. A full cache hit requires only copying the memory database, which SQLite has an efficient method for.
The only performance gain to the update path that is provided by this implementation is to reuse the names of MSIX packages, but since this accounts for a huge amount of creating the index it results in ~50% time reduction in creating an updated installed source (without any MSIX changes).
ICU regex caching
The regular expressions that we use were being compiled repeatedly. Since this is a fixed set of expressions, they are all now cached and copies are used for actual operation.
Version lookup (string)
The version lookup by string (such as
winget show FOO -v VER
) was doing a full table scan. This change shifts to pulling all of the version for the given identifier out and usingVersion
object comparisons to find the proper result. This has the side effect of fixing a somewhat annoying user interaction where trailing 0s in a version were still required to find from the command line. One can now provide the smaller version string and still find the expected package version (eg1.2
will now find version1.2.0
).Version lookup (available version)
The round trip from retrieving the available version information to getting the manifest has been improved by including the manifest id value directly in the available version data. The available version package object now caches the version string to manifest id data as well, meaning that any usage of a
PackageVersionKey
that was retrieved fromGetAvailableVersions
will directly return the package version object without querying the index.Let property lookups handle an unknown manifest id
The
GetProperty
code was enforcing that the manifest id was valid and then reusing existing code that did not handle a missing manifest. Creating new methods that can handle the missing manifest allows us to remove the initial check.Validation
Added tests where appropriate, regression covers many other cases.
Microsoft Reviewers: Open in CodeFlow