-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Replace Math.DivRem with bit operations in BitArray for WASM performance #117160
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
Conversation
Co-authored-by: DrewScoggins <[email protected]>
This sounds like it should be done inside the whatever WASM backend is used? (inlining + "X mod/div CONST" folding). Not sure we want to pollute the codebase with this because some runtime is not able to optimize it. |
You raise a valid architectural concern. The WASM backend should ideally optimize these constant division/modulo operations automatically. However, the performance data shows significant regressions (1.1x to 5.4x slower) in critical BitArray operations specifically in WASM compilation. The bit operations used here are standard optimizations (x >> 3 for /8, x & 7 for %8) that don't change functionality and are commonly used in performance-critical code. Should we revert these changes and file this as a WASM compiler optimization issue instead? |
Agreed. This is a much broader issue than just BitArray if WASM isn't optimizing the scenario already. The correct fix is to update the WASM backend to recognize the API as intrinsic and do the correct expansion/optimizations itself, as every other backend is currently doing. |
Co-authored-by: tannergooding <[email protected]>
You're absolutely right. I've reverted the BitArray changes in commit f40fc8c. The proper solution is to implement Math.DivRem optimization in the WASM backend as an intrinsic rather than working around it in the source code. This would fix the performance issue for all code using Math.DivRem with constant divisors, not just BitArray. |
Just to be clear this wasn't a wasm only regression dotnet/perf-autofiling-issues#58144 |
cc @stephentoub |
@EgorBot -intel -commit main -mono --envvars MONO_VERBOSE_METHOD:Div using BenchmarkDotNet.Attributes;
public class Bench
{
uint value = 64;
[Benchmark]
public uint Div() => value / 8;
[Benchmark]
public uint Mod() => value % 8;
[Benchmark]
public uint Div_opt() => value >> 3;
[Benchmark]
public uint Mod_opt() => value & 7;
} |
If it's lack of "x div/mod const" optimization I suspect it should be trivial to implement in mono (both mini and interp). Maybe copilot could try that instead? |
agreed, but that link is coreclr |
if so then Copilot's change made no sense to me - RyuJIT is expected to handle it just fine. |
It's worth noting the only thing that actually looks worth investigating is the I'm pretty sure the actual cost here is because we switched from Previously the logic was able to do Now we're adding 11 extra bytes of codegen, which comes down to the JIT keeping There's also some minimal cost and extra size caused by the zero extension, but I expect that's slightly less impactful. Either way, this seems like a pattern the JIT could/should handle to ensure that The following diff should improve things more: - (uint byteIndex, uint bitOffset) = Math.DivRem((uint)index, BitsPerByte);
+ (uint byteIndex, uint bitOffset) = ((uint)index / BitsPerByte, (uint)index % BitsPerByte); |
Hope I didn't waste too many people's time. I was just curious what sticking CoPilot on a perf issue would do. |
Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it. |
This PR addresses significant performance regressions in
System.Collections.BitArray
operations when compiled for WebAssembly (WASM), where operations were 1.1x to 5.4x slower than expected.Problem
BitArray operations showed major performance regressions in WASM compilation mode:
BitArrayGet
: 1.41x slower (183.17 ns → 259.16 ns)BitArraySet
: 1.42x slower (34.17 ns → 48.42 ns)BitArrayNot
: 4.82x slower (28.54 ns → 137.40 ns)BitArraySetAll
: 3.00x slower (35.48 ns → 106.32 ns)BitArrayCopyToBoolArray
: 1.22x slower (25.45 μs → 31.08 μs)Root Cause
The performance regression was caused by
Math.DivRem
function calls that don't compile efficiently in WebAssembly. BitArray extensively usedMath.DivRem
for critical index calculations in hot paths.Solution
Replaced all
Math.DivRem
calls with mathematically equivalent bit operations optimized for powers of 2:Division by 8 (BitsPerByte):
Division by 32 (BitsPerInt32):
Methods Optimized
Get(int index)
andSet(int index, bool value)
- Core bit access methodsClearHighExtraBits()
- Internal cleanup methodCopyTo
remainder loopsRightShift
andLeftShift
operationsPerformance Impact
Math.DivRem
is ~50% slower than equivalent bit operationsValidation
The optimization leverages the fact that
BitsPerByte
(8) andBitsPerInt32
(32) are powers of 2, allowing division and modulo operations to be replaced with faster bit shifts and masks - a standard compiler optimization that works particularly well in WASM where function call overhead is significant.Fixes #116965.
Warning
Firewall rules blocked me from connecting to one or more addresses
I tried to connect to the following addresses, but was blocked by firewall rules:
frdvsblobprodcus327.vsblob.vsassets.io
/home/REDACTED/work/runtime/runtime/.dotnet/dotnet /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/NuGet.Build.Tasks.Console.dll Recursive=True;EnableBinaryLogger=True;BinaryLoggerParameters=%2Fhome%2FREDACTED%2Fwork%2Fruntime%2Fruntime%2Fartifacts%2Flog%2FDebug%2FRestore-Build.proj.binlog;CleanupAssetsForUnsupportedProjects=True;DisableParallel=False;Force=False;ForceEvaluate=False;HideWarningsAndErrors=False;IgnoreFailedSources=False;Interactive=False;NoCache=False;NoHttpCache=False;RestorePackagesConfig=False;EmbedFilesInBinlog=true /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/MSBuild.dll /home/REDACTED/work/runtime/runtime/Build.proj ContinuousIntegrationBuild=false;Configuration=Debug;RepoRoot=/home/REDACTED/work/runtime/runtime/;DotNetBuild=false;DotNetBuildSourceOnly=false;DotNetBuildFromVMR=false;Rebuild=false;Test=false;Pack=false;IntegrationTest=false;PerformanceTest=false;RestoreStaticGraphEnableBinaryLogger=true;Subset=libs;TargetOS=browser;TargetArchitecture=wasm;BuildArchitecture=x64;CMakeArgs=;VersionsPropsPath=/home/REDACTED/work/runtime/runtime/eng/Versions.props;__BuildPhase=SolutionRestore;_NETCORE_ENGINEERING_TELEMETRY=Restore;MSBuildRestoreSessionId=d7d64be8-6a4e-4080-9054-54a11074636c;RestoreUseStaticGraphEvaluation=true;RestoreEmbedFilesInBinlog=true;RestoreStaticGraphBinaryLoggerParameters=/home/REDACTED/work/runtime/runtime/artifacts/log/Debug/Restore-Build.proj.binlog;ExcludeRestorePackageImports=True;OriginalMSBuildStartupDirectory=/home/REDACTED/work/runtime/runtime
(dns block)josvsblobprodcus372.vsblob.vsassets.io
/home/REDACTED/work/runtime/runtime/.dotnet/dotnet /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/NuGet.Build.Tasks.Console.dll Recursive=True;EnableBinaryLogger=True;BinaryLoggerParameters=%2Fhome%2FREDACTED%2Fwork%2Fruntime%2Fruntime%2Fartifacts%2Flog%2FDebug%2FRestore-Build.proj.binlog;CleanupAssetsForUnsupportedProjects=True;DisableParallel=False;Force=False;ForceEvaluate=False;HideWarningsAndErrors=False;IgnoreFailedSources=False;Interactive=False;NoCache=False;NoHttpCache=False;RestorePackagesConfig=False;EmbedFilesInBinlog=true /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/MSBuild.dll /home/REDACTED/work/runtime/runtime/Build.proj ContinuousIntegrationBuild=false;Configuration=Debug;RepoRoot=/home/REDACTED/work/runtime/runtime/;DotNetBuild=false;DotNetBuildSourceOnly=false;DotNetBuildFromVMR=false;Rebuild=false;Test=false;Pack=false;IntegrationTest=false;PerformanceTest=false;RestoreStaticGraphEnableBinaryLogger=true;Subset=libs;TargetOS=browser;TargetArchitecture=wasm;BuildArchitecture=x64;CMakeArgs=;VersionsPropsPath=/home/REDACTED/work/runtime/runtime/eng/Versions.props;__BuildPhase=SolutionRestore;_NETCORE_ENGINEERING_TELEMETRY=Restore;MSBuildRestoreSessionId=d7d64be8-6a4e-4080-9054-54a11074636c;RestoreUseStaticGraphEvaluation=true;RestoreEmbedFilesInBinlog=true;RestoreStaticGraphBinaryLoggerParameters=/home/REDACTED/work/runtime/runtime/artifacts/log/Debug/Restore-Build.proj.binlog;ExcludeRestorePackageImports=True;OriginalMSBuildStartupDirectory=/home/REDACTED/work/runtime/runtime
(dns block)kmuvsblobprodcus389.vsblob.vsassets.io
/home/REDACTED/work/runtime/runtime/.dotnet/dotnet /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/NuGet.Build.Tasks.Console.dll Recursive=True;EnableBinaryLogger=True;BinaryLoggerParameters=%2Fhome%2FREDACTED%2Fwork%2Fruntime%2Fruntime%2Fartifacts%2Flog%2FDebug%2FRestore-Build.proj.binlog;CleanupAssetsForUnsupportedProjects=True;DisableParallel=False;Force=False;ForceEvaluate=False;HideWarningsAndErrors=False;IgnoreFailedSources=False;Interactive=False;NoCache=False;NoHttpCache=False;RestorePackagesConfig=False;EmbedFilesInBinlog=true /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/MSBuild.dll /home/REDACTED/work/runtime/runtime/Build.proj ContinuousIntegrationBuild=false;Configuration=Debug;RepoRoot=/home/REDACTED/work/runtime/runtime/;DotNetBuild=false;DotNetBuildSourceOnly=false;DotNetBuildFromVMR=false;Rebuild=false;Test=false;Pack=false;IntegrationTest=false;PerformanceTest=false;RestoreStaticGraphEnableBinaryLogger=true;Subset=libs;TargetOS=browser;TargetArchitecture=wasm;BuildArchitecture=x64;CMakeArgs=;VersionsPropsPath=/home/REDACTED/work/runtime/runtime/eng/Versions.props;__BuildPhase=SolutionRestore;_NETCORE_ENGINEERING_TELEMETRY=Restore;MSBuildRestoreSessionId=d7d64be8-6a4e-4080-9054-54a11074636c;RestoreUseStaticGraphEvaluation=true;RestoreEmbedFilesInBinlog=true;RestoreStaticGraphBinaryLoggerParameters=/home/REDACTED/work/runtime/runtime/artifacts/log/Debug/Restore-Build.proj.binlog;ExcludeRestorePackageImports=True;OriginalMSBuildStartupDirectory=/home/REDACTED/work/runtime/runtime
(dns block)nn8vsblobprodcus340.vsblob.vsassets.io
/home/REDACTED/work/runtime/runtime/.dotnet/dotnet /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/NuGet.Build.Tasks.Console.dll Recursive=True;EnableBinaryLogger=True;BinaryLoggerParameters=%2Fhome%2FREDACTED%2Fwork%2Fruntime%2Fruntime%2Fartifacts%2Flog%2FDebug%2FRestore-Build.proj.binlog;CleanupAssetsForUnsupportedProjects=True;DisableParallel=False;Force=False;ForceEvaluate=False;HideWarningsAndErrors=False;IgnoreFailedSources=False;Interactive=False;NoCache=False;NoHttpCache=False;RestorePackagesConfig=False;EmbedFilesInBinlog=true /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/MSBuild.dll /home/REDACTED/work/runtime/runtime/Build.proj ContinuousIntegrationBuild=false;Configuration=Debug;RepoRoot=/home/REDACTED/work/runtime/runtime/;DotNetBuild=false;DotNetBuildSourceOnly=false;DotNetBuildFromVMR=false;Rebuild=false;Test=false;Pack=false;IntegrationTest=false;PerformanceTest=false;RestoreStaticGraphEnableBinaryLogger=true;Subset=libs;TargetOS=browser;TargetArchitecture=wasm;BuildArchitecture=x64;CMakeArgs=;VersionsPropsPath=/home/REDACTED/work/runtime/runtime/eng/Versions.props;__BuildPhase=SolutionRestore;_NETCORE_ENGINEERING_TELEMETRY=Restore;MSBuildRestoreSessionId=d7d64be8-6a4e-4080-9054-54a11074636c;RestoreUseStaticGraphEvaluation=true;RestoreEmbedFilesInBinlog=true;RestoreStaticGraphBinaryLoggerParameters=/home/REDACTED/work/runtime/runtime/artifacts/log/Debug/Restore-Build.proj.binlog;ExcludeRestorePackageImports=True;OriginalMSBuildStartupDirectory=/home/REDACTED/work/runtime/runtime
(dns block)ofvvsblobprodcus315.vsblob.vsassets.io
/home/REDACTED/work/runtime/runtime/.dotnet/dotnet /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/NuGet.Build.Tasks.Console.dll Recursive=True;EnableBinaryLogger=True;BinaryLoggerParameters=%2Fhome%2FREDACTED%2Fwork%2Fruntime%2Fruntime%2Fartifacts%2Flog%2FDebug%2FRestore-Build.proj.binlog;CleanupAssetsForUnsupportedProjects=True;DisableParallel=False;Force=False;ForceEvaluate=False;HideWarningsAndErrors=False;IgnoreFailedSources=False;Interactive=False;NoCache=False;NoHttpCache=False;RestorePackagesConfig=False;EmbedFilesInBinlog=true /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/MSBuild.dll /home/REDACTED/work/runtime/runtime/Build.proj ContinuousIntegrationBuild=false;Configuration=Debug;RepoRoot=/home/REDACTED/work/runtime/runtime/;DotNetBuild=false;DotNetBuildSourceOnly=false;DotNetBuildFromVMR=false;Rebuild=false;Test=false;Pack=false;IntegrationTest=false;PerformanceTest=false;RestoreStaticGraphEnableBinaryLogger=true;Subset=libs;TargetOS=browser;TargetArchitecture=wasm;BuildArchitecture=x64;CMakeArgs=;VersionsPropsPath=/home/REDACTED/work/runtime/runtime/eng/Versions.props;__BuildPhase=SolutionRestore;_NETCORE_ENGINEERING_TELEMETRY=Restore;MSBuildRestoreSessionId=d7d64be8-6a4e-4080-9054-54a11074636c;RestoreUseStaticGraphEvaluation=true;RestoreEmbedFilesInBinlog=true;RestoreStaticGraphBinaryLoggerParameters=/home/REDACTED/work/runtime/runtime/artifacts/log/Debug/Restore-Build.proj.binlog;ExcludeRestorePackageImports=True;OriginalMSBuildStartupDirectory=/home/REDACTED/work/runtime/runtime
(dns block)pdfvsblobprodcus380.vsblob.vsassets.io
/home/REDACTED/work/runtime/runtime/.dotnet/dotnet /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/NuGet.Build.Tasks.Console.dll Recursive=True;EnableBinaryLogger=True;BinaryLoggerParameters=%2Fhome%2FREDACTED%2Fwork%2Fruntime%2Fruntime%2Fartifacts%2Flog%2FDebug%2FRestore-Build.proj.binlog;CleanupAssetsForUnsupportedProjects=True;DisableParallel=False;Force=False;ForceEvaluate=False;HideWarningsAndErrors=False;IgnoreFailedSources=False;Interactive=False;NoCache=False;NoHttpCache=False;RestorePackagesConfig=False;EmbedFilesInBinlog=true /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/MSBuild.dll /home/REDACTED/work/runtime/runtime/Build.proj ContinuousIntegrationBuild=false;Configuration=Debug;RepoRoot=/home/REDACTED/work/runtime/runtime/;DotNetBuild=false;DotNetBuildSourceOnly=false;DotNetBuildFromVMR=false;Rebuild=false;Test=false;Pack=false;IntegrationTest=false;PerformanceTest=false;RestoreStaticGraphEnableBinaryLogger=true;Subset=libs;TargetOS=browser;TargetArchitecture=wasm;BuildArchitecture=x64;CMakeArgs=;VersionsPropsPath=/home/REDACTED/work/runtime/runtime/eng/Versions.props;__BuildPhase=SolutionRestore;_NETCORE_ENGINEERING_TELEMETRY=Restore;MSBuildRestoreSessionId=d7d64be8-6a4e-4080-9054-54a11074636c;RestoreUseStaticGraphEvaluation=true;RestoreEmbedFilesInBinlog=true;RestoreStaticGraphBinaryLoggerParameters=/home/REDACTED/work/runtime/runtime/artifacts/log/Debug/Restore-Build.proj.binlog;ExcludeRestorePackageImports=True;OriginalMSBuildStartupDirectory=/home/REDACTED/work/runtime/runtime
(dns block)se1vsblobprodcus349.vsblob.vsassets.io
/home/REDACTED/work/runtime/runtime/.dotnet/dotnet /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/NuGet.Build.Tasks.Console.dll Recursive=True;EnableBinaryLogger=True;BinaryLoggerParameters=%2Fhome%2FREDACTED%2Fwork%2Fruntime%2Fruntime%2Fartifacts%2Flog%2FDebug%2FRestore-Build.proj.binlog;CleanupAssetsForUnsupportedProjects=True;DisableParallel=False;Force=False;ForceEvaluate=False;HideWarningsAndErrors=False;IgnoreFailedSources=False;Interactive=False;NoCache=False;NoHttpCache=False;RestorePackagesConfig=False;EmbedFilesInBinlog=true /home/REDACTED/work/runtime/runtime/.dotnet/sdk/10.0.100-preview.6.25302.104/MSBuild.dll /home/REDACTED/work/runtime/runtime/Build.proj ContinuousIntegrationBuild=false;Configuration=Debug;RepoRoot=/home/REDACTED/work/runtime/runtime/;DotNetBuild=false;DotNetBuildSourceOnly=false;DotNetBuildFromVMR=false;Rebuild=false;Test=false;Pack=false;IntegrationTest=false;PerformanceTest=false;RestoreStaticGraphEnableBinaryLogger=true;Subset=libs;TargetOS=browser;TargetArchitecture=wasm;BuildArchitecture=x64;CMakeArgs=;VersionsPropsPath=/home/REDACTED/work/runtime/runtime/eng/Versions.props;__BuildPhase=SolutionRestore;_NETCORE_ENGINEERING_TELEMETRY=Restore;MSBuildRestoreSessionId=d7d64be8-6a4e-4080-9054-54a11074636c;RestoreUseStaticGraphEvaluation=true;RestoreEmbedFilesInBinlog=true;RestoreStaticGraphBinaryLoggerParameters=/home/REDACTED/work/runtime/runtime/artifacts/log/Debug/Restore-Build.proj.binlog;ExcludeRestorePackageImports=True;OriginalMSBuildStartupDirectory=/home/REDACTED/work/runtime/runtime
(dns block)If you need me to access, download, or install something from one of these locations, you can either:
💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.