简体   繁体   English

使用 MemoryStream 和 CryptoStream 时清除 Gen2 和 LOH 中的堆

[英]Clear Heap in Gen2 & LOH when using MemoryStream & CryptoStream

I'm trying to get some measurements to know how much footprint if "encryption/decryption process" added\\.如果添加了“加密/解密过程”,我正在尝试进行一些测量以了解有多少足迹。 Also I'm comparing different approaches like using FileStream or returning MemoryStream (which I need in some cases).我还在比较不同的方法,比如使用FileStream或返回MemoryStream (在某些情况下我需要)。

Looks like large files are kept in Memory (Gen2 & LOH).看起来大文件保存在内存中(Gen2 & LOH)。 How could I clear heap completely (I want to see same Gen2 results in FileStream Approach)?我怎么能完全清除堆(我想在 FileStream 方法中看到相同的 Gen2 结果)?

I'm using the using keyword.我正在使用using关键字。 But looks like there is no hope with that!但看起来没有希望! I also reduced the default Buffer Size as you can see the code below.我还减少了默认的缓冲区大小,你可以看到下面的代码。 But I still have numbers in Gen2但我在 Gen2 中还有数字

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.572 (2004/?/20H1)
Intel Core i9-10920X CPU 3.50GHz, 1 CPU, 24 logical and 12 physical cores
  [Host]     : .NET Framework 4.8 (4.8.4250.0), X86 LegacyJIT
  DefaultJob : .NET Framework 4.8 (4.8.4250.0), X86 LegacyJIT

File Stream Results文件流结果

|              Method |      Mean |     Error |    StdDev |     Gen 0 |    Gen 1 | Gen 2 |   Allocated |
|-------------------- |----------:|----------:|----------:|----------:|---------:|------:|------------:|
| TXT300BYTES_Decrypt |  2.500 ms | 0.0444 ms | 0.0593 ms |   19.5313 |        - |     - |   105.11 KB |
|    PDF500KB_Decrypt | 12.909 ms | 0.2561 ms | 0.4348 ms |  187.5000 |  15.6250 |     - |  1019.59 KB |
|      PDF1MB_Decrypt | 14.125 ms | 0.2790 ms | 0.4001 ms |  406.2500 |  15.6250 |     - |  2149.96 KB |
|     TIFF1MB_Decrypt | 10.087 ms | 0.1949 ms | 0.1728 ms |  437.5000 |  31.2500 |     - |  2329.37 KB |
|     TIFF5MB_Decrypt | 22.779 ms | 0.4316 ms | 0.4239 ms | 2000.0000 | 187.5000 |     - | 10434.34 KB |
|    TIFF10MB_Decrypt | 38.467 ms | 0.7382 ms | 0.8205 ms | 3857.1429 | 285.7143 |     - | 20144.01 KB |

Memory Stream Results内存流结果

|              Method |      Mean |     Error |    StdDev |     Gen 0 |     Gen 1 |    Gen 2 |   Allocated |
|-------------------- |----------:|----------:|----------:|----------:|----------:|---------:|------------:|
| TXT300BYTES_Decrypt |  1.673 ms | 0.0098 ms | 0.0092 ms |   27.3438 |    1.9531 |        - |   147.69 KB |
|    PDF500KB_Decrypt |  9.956 ms | 0.1407 ms | 0.1248 ms |  328.1250 |  328.1250 | 328.1250 |  2316.08 KB |
|      PDF1MB_Decrypt | 11.998 ms | 0.0622 ms | 0.0486 ms |  921.8750 |  546.8750 | 531.2500 |   4737.8 KB |
|     TIFF1MB_Decrypt |  9.252 ms | 0.0973 ms | 0.0910 ms |  953.1250 |  671.8750 | 500.0000 |  4902.34 KB |
|     TIFF5MB_Decrypt | 24.220 ms | 0.1105 ms | 0.0980 ms | 2531.2500 |  718.7500 | 468.7500 | 20697.43 KB |
|    TIFF10MB_Decrypt | 41.463 ms | 0.5678 ms | 0.5033 ms | 4833.3333 | 1500.0000 | 916.6667 | 40696.31 KB |
public static class Constants
{
    public const int BufferSize = 40960; // Default is  81920
}

File Decrypt Method文件解密方法

public class DescryptionService
{
    public async Task<string> DecryptFileAsync(string sourcePath)
    {
        var tempFilePath = SecurityFileHelper.CreateTempFile();
        using var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read);
        var keyBytes = Convert.FromBase64String(_key);
        using var destinationStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
        using var provider = new AesCryptoServiceProvider();

        var IV = new byte[provider.IV.Length];
        await sourceStream.ReadAsync(IV, 0, IV.Length);

        using var cryptoTransform = provider.CreateDecryptor(keyBytes, IV);
        using var cryptoStream = new CryptoStream(sourceStream, cryptoTransform, CryptoStreamMode.Read);
        await cryptoStream.CopyToAsync(destinationStream, Constants.BufferSize);

        return tempFilePath;
    }
}

Memory Decrypt Method内存解密方法

public class DescryptionService
{
    public async Task<Stream> DecryptStreamAsync(Stream sourceStream)
    {
        var memoryStream = new MemoryStream();

        if (sourceStream.Position != 0) sourceStream.Position = 0;
        var tempFilePath = SecurityFileHelper.CreateTempFile();
        try
        {
            var keyBytes = Convert.FromBase64String(_key);
            using var destinationStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            using var provider = new AesCryptoServiceProvider();

            var IV = new byte[provider.IV.Length];
            await sourceStream.ReadAsync(IV, 0, IV.Length);

            using var cryptoTransform = provider.CreateDecryptor(keyBytes, IV);
            using var cryptoStream = new CryptoStream(sourceStream, cryptoTransform, CryptoStreamMode.Read);
            await cryptoStream.CopyToAsync(destinationStream, Constants.BufferSize);
            destinationStream.Position = 0;
            await destinationStream.CopyToAsync(memoryStream, Constants.BufferSize);
            await memoryStream.FlushAsync();
            memoryStream.Position = 0;
        }
        finally
        {
            if (File.Exists(tempFilePath))
                File.Delete(tempFilePath);
        }
        return memoryStream;
    }
}
// Calling it like this
using var encryptedStream = File.OpenRead("some file path");
var svc = new DecryptionService();
using var decryptedStream = await svc.DecryptStreamAsync(encryptedStream);

By the way I also added these lines:顺便说一句,我还添加了这些行:

decryptedStream.Position = 0;
decryptedStream.SetLength(0);
decryptedStream.Capacity = 0; // <<< this one will null bytes in memory stream

And still have these results仍然有这些结果

|              Method |      Mean |     Error |    StdDev |    Median |     Gen 0 |     Gen 1 |    Gen 2 |   Allocated |
|-------------------- |----------:|----------:|----------:|----------:|----------:|----------:|---------:|------------:|
| TXT300BYTES_Decrypt |  1.659 ms | 0.0322 ms | 0.0301 ms |  1.662 ms |   27.3438 |    1.9531 |        - |   148.03 KB |
|    PDF500KB_Decrypt | 11.085 ms | 0.2829 ms | 0.8297 ms | 10.769 ms |  328.1250 |  328.1250 | 328.1250 |  2312.33 KB |
|      PDF1MB_Decrypt | 12.479 ms | 0.2029 ms | 0.3859 ms | 12.402 ms |  906.2500 |  562.5000 | 531.2500 |  4734.61 KB |
|     TIFF1MB_Decrypt |  9.352 ms | 0.0971 ms | 0.0861 ms |  9.359 ms |  953.1250 |  593.7500 | 500.0000 |     4908 KB |
|     TIFF5MB_Decrypt | 24.760 ms | 0.4752 ms | 0.4213 ms | 24.607 ms | 2593.7500 |  843.7500 | 531.2500 | 20715.76 KB |
|    TIFF10MB_Decrypt | 41.976 ms | 0.6657 ms | 0.5901 ms | 42.011 ms | 4833.3333 | 1500.0000 | 916.6667 | 40744.43 KB |

What did I miss?!我错过了什么?! :( :(

I'm comparing different approaches like using FileStream or returning MemoryStream我正在比较不同的方法,比如使用 FileStream 或返回 MemoryStream

Looks like large files are kept in Memory (Gen2 & LOH).看起来大文件保存在内存中(Gen2 & LOH)。 How could I clear heap completely (I want to see same Gen2 results in FileStream Approach)?我怎么能完全清除堆(我想在 FileStream 方法中看到相同的 Gen2 结果)?

I am not sure if I understand what you mean by clearing the heap and why do you want to see Gen 2 collections for FileStream .我不确定我是否理解清除堆的意思,以及为什么要查看FileStream Gen 2 集合。

The harness that you are using (BenchmarkDotNet) enforces two full memory cleanups after every benchmark iteration .您使用的工具 (BenchmarkDotNet) 在每次基准测试迭代后强制执行两次完整的内存清理 It ensures that every benchmarking iteration starts with a "clean heap".它确保每个基准测试迭代都从“干净的堆”开始。 To ensure that the self-tuning nature of GC (or any other things like memory leaks) is not affecting other benchmarks, every benchmark is executed in a stand-alone process.为确保 GC 的自调整特性(或任何其他诸如内存泄漏之类的事情)不会影响其他基准测试,每个基准测试都在独立进程中执行。 Moreover, the number of collections as scaled per 1k operations (benchmark invocations).此外,每 1k 次操作(基准调用)缩放的集合数量。 This allows for an apples-to-apples comparison of the GC metrics.这允许对 GC 指标进行逐个比较。

You are comparing two different approaches and most probably (this is a hypothesis that needs to be verified with a memory profiler) one of them allocates large objects and hence you get Gen 2 collections.您正在比较两种不同的方法,并且很可能(这是一个需要用内存分析器验证的假设)其中一个分配大对象,因此您获得Gen 2集合。 The other does not.另一个没有。 It's a performance characteristic of a given solution and you should just take it under consideration when implementing the business logic.这是给定解决方案的性能特征,您应该在实现业务逻辑时考虑它。 For example: if your service is supposed to be low latency and you can't allow for long GC pauses caused by Gen 2 collections you should choose the approach that is not allocating large objects.例如:如果您的服务应该是低延迟的,并且您不能允许由 Gen 2 收集引起的长时间 GC 暂停,您应该选择不分配大对象的方法。

If you want to get rid of Gen 2 collections you can try pooling the memory by using:如果你想摆脱 Gen 2 集合,你可以尝试使用以下方法来池化内存:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM