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
[System.Convert]::FromBase64String causes memory leak with large strings #101061
Comments
Tagging subscribers to this area: @dotnet/area-system-runtime |
I see this taking between 3 and 6 seconds with dotnet-sdk-8.0.204-win-x64.exe and PowerShell 7.4.2 on Windows 10. I wonder if you have some kind of antivirus software interfering with the operation. Is it equally slow with a random-data file of the same size? |
The other copy is PowerShell/PowerShell#21473. Linking it here so that people can more easily check what has been investigated already. |
How much RAM does the computer have? From my reading of the code, the only heap allocation in [System.Convert]::FromBase64String should be for the byte array that it returns. Is it possible that the [System.Convert]::FromBase64String method is somehow replaced (perhaps in PowerShell startup scripts) and the call goes to an implementation different from what is in .NET Runtime? |
I retested the following updated script on my desktop PC. A Ryzen 5800X with 128 GB of RAM and PCIe Gen4 NVMe storage. The test ran much faster as expected, but the memory usage still remains high. Even after invoking Reading the 222 MB file into memory takes 0.06 seconds and converting it to base64 takes 0.22 seconds and uses 845 MB of RAM across both operations as expected. The last operation Updated test script: $name = "random.bin"
$start = Get-Date
Write-Host "Creating Path to $name test file: " -NoNewline
$now = Get-Date
$file = Join-Path -Path $PSScriptRoot -ChildPath $name
Write-Host " $(((Get-Date) - $now).TotalMilliseconds) ms"
Write-Host "Reading all file bytes into memory: " -NoNewline
$now = Get-Date
$bytes = [System.IO.File]::ReadAllBytes( $file )
Write-Host " $(((Get-Date) - $now).TotalMilliseconds) ms"
Write-Host "Converting file bytes to base64 string: " -NoNewline
$now = Get-Date
$base64 = [System.Convert]::ToBase64String( $bytes )
Write-Host " $(((Get-Date) - $now).TotalMilliseconds) ms"
Write-Host "Converting base64 string back to file bytes: " -NoNewline
$now = Get-Date
$bytes = [System.Convert]::FromBase64String( $base64 )
Write-Host " $(((Get-Date) - $now).TotalMilliseconds) ms"
Write-Host "Test complete"
Write-Host "Total duration: $(((Get-Date) - $start).TotalMilliseconds) ms" |
So it appears that this is actually a PowerShell issue, not a dotnet/runtime issue. When I repeat the same exact test, using a console application written in C#, it runs normally and there's no memory leak. Below are the testing times using a C# console application. Total memory usage is around 1.0 GB which is expected for creating four copies of a 222 MB file in memory, two of which are base64 encoded. This was tested using the .NET 8.0 run-time for Windows x64, the latest version available on the dotnet website for download as of today. using System;
namespace Testing {
public static class Program {
public static void Main(string[] args) {
var start = DateTime.Now;
Console.Write( "ReadAllBytes: " );
var now = DateTime.Now;
var file = System.IO.File.ReadAllBytes( @"C:\Github\memleak\random.bin" );
var duration = DateTime.Now - now;
Console.WriteLine( "{0:#,000.00} ms", duration.TotalMilliseconds );
Console.Write( "ToBase64String: " );
now = DateTime.Now;
var text = System.Convert.ToBase64String( file );
duration = DateTime.Now - now;
Console.WriteLine( "{0:#,000.00} ms", duration.TotalMilliseconds );
Console.Write( "FromBase64String: " );
now = DateTime.Now;
var bin = System.Convert.FromBase64String( text );
duration = DateTime.Now - now;
Console.WriteLine( "{0:#,000.00} ms", duration.TotalMilliseconds );
Console.WriteLine( "Press Enter to call GC.Collect()" );
Console.ReadLine();
file = null;
text = null;
bin = null;
GC.Collect();
Console.WriteLine( "Press Enter to close process" );
Console.ReadLine();
}
}
} |
I suggest closing this dotnet/runtime issue, because the slowdown is not caused by code in this repository and cannot be fixed here; PowerShell/PowerShell#21473 (comment) shows it is caused by PowerShell calling the AMSI provider of Windows Defender. |
Description
Calling [System.Convert]::FromBase64String on a large string, such as one that is over 300 million characters long, appears to leak memory and requires a significant amount of time when compared to doing the same thing with the .NET Framework.
Reproduction Steps
This was tested on PowerShell 7.4.2
NOTE: That if you test this with a newer version of the .NET 8.0 installer, you may have to modify the test script to pick the correct file for the test since the filename is hard coded on line 3.
Expected behavior
The .NET 8.0 installer for Windows x64 is approximately 222 MB in size. Reading into memory and converting to base64 then converting back should require about 790 MB of RAM with all variables remaining in scope during the process and no garbage collection happening or object disposal happening. The observed behavior appears to be memory-leak related as the amount of memory used once the conversion eventually completes is about 3.4 GB of RAM. These data points can be see in the attached screen shots.
Actual behavior
When you run the same script in PowerShell 7 and Windows PowerShell 5.1, you see two very different behaviors:
In PowerShell 7.4.2, the time to complete is 82 seconds and memory used is 3.4 GB
In PowerShell 5.1, the time to complete is 7 seconds and memory used is 1.0 GB
This suggests there is an error in the PowerShell 7.4.2 / .NET 8.0 implementation. This bug has been reported in both the PowerShell 7 issues list and the dotnet/runtime issues list.
Regression?
No response
Known Workarounds
No response
Configuration
.NET version is .NET 8.0.204 Windows x64
Test environment is Windows 11 Professional 64-bit edition
Other information
Testing in PowerShell 7.4.2
Testing in Windows PowerShell 5.1
PowerShell 7.4.2 Memory Usage
PowerShell 5.1 Memory Usage
The text was updated successfully, but these errors were encountered: