You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

生成1-100GB内存映射IO文件时遇内存不足异常求解决方案

问题分析与解决方案

你的代码触发内存不足的核心原因是生成随机数据时先在内存中用StringBuilder攒下全部内容,再转成byte[]——这会在内存中同时保存两份超大数据(字符串采用UTF-16编码,2GB的UTF-8字节对应约4GB的字符串内存),远超系统剩余的3GB空闲内存。此外还有随机数生成效率低、分区边界处理遗漏等问题,以下是针对性修改:

核心优化点

  • 避免内存预存全量数据:直接向内存映射视图写入字节,无需先把所有数据攒到StringBuilder或byte[]中
  • 复用Random实例:每次创建Random会导致随机序列重复,且频繁实例化影响性能
  • 修正分区计算逻辑:处理总大小无法被分区大小整除的剩余部分
  • 优化随机文本生成:直接生成字节,减少字符串转码的内存开销和耗时

修改后的完整代码

using System;
using System.IO;
using System.MemoryMappedFiles;
using System.Text;
using System.Diagnostics;

public class RandomLinesGenerator
{
    // 复用全局Random实例,避免重复序列和频繁实例化开销
    private static readonly Random _random = new Random();
    private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    private static void GenerateRandomTextLine(Span<byte> buffer, out int bytesWritten)
    {
        int length = _random.Next(1, 151);
        bytesWritten = 0;

        for (int i = 0; i < length; i++)
        {
            char c = _chars[_random.Next(_chars.Length)];
            // 直接把字符转成UTF-8字节写入缓冲区
            bytesWritten += Encoding.UTF8.GetBytes(new[] { c }, 0, 1, buffer.Slice(bytesWritten), 0);
        }
    }

    public static void WriteRandomBytesToAccessor(MemoryMappedViewAccessor accessor, long desiredByteCount, int prefixIntMinValue, int prefixIntMaxValue)
    {
        long bytesWritten = 0;
        byte[] newlineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
        byte[] numberBuffer = new byte[10]; // 足够存储2500以内的数字字符串转UTF8

        while (bytesWritten < desiredByteCount)
        {
            // 生成随机前缀数字并转成UTF8字节
            int randomNumber = _random.Next(prefixIntMinValue, prefixIntMaxValue + 1);
            int numberByteCount = Encoding.UTF8.GetBytes(randomNumber.ToString(), 0, randomNumber.ToString().Length, numberBuffer, 0);
            
            // 计算当前行所需的总字节数(数字+空格+随机文本+换行)
            int maxPossibleTextLength = (int)(desiredByteCount - bytesWritten - numberByteCount - 1 - newlineBytes.Length);
            if (maxPossibleTextLength <= 0)
                break;

            // 生成随机文本并写入临时缓冲区
            Span<byte> textBuffer = stackalloc byte[maxPossibleTextLength];
            GenerateRandomTextLine(textBuffer, out textByteCount);

            // 计算当前行总字节数
            long lineTotalBytes = numberByteCount + 1 + textByteCount + newlineBytes.Length;
            if (bytesWritten + lineTotalBytes > desiredByteCount)
                break;

            // 写入数字
            accessor.WriteArray(bytesWritten, numberBuffer, 0, numberByteCount);
            bytesWritten += numberByteCount;

            // 写入空格
            accessor.Write(bytesWritten, (byte)' ');
            bytesWritten += 1;

            // 写入随机文本
            accessor.WriteArray(bytesWritten, textBuffer.ToArray(), 0, textByteCount);
            bytesWritten += textByteCount;

            // 写入换行
            accessor.WriteArray(bytesWritten, newlineBytes, 0, newlineBytes.Length);
            bytesWritten += newlineBytes.Length;
        }
    }
}

public class MemoryMappedIOTechnique
{
    private static readonly string _fileLocation = Path.Combine("Resources", "files");

    private long GetPartitionSize(long totalFileSize)
    {
        // 使用更可靠的内存获取方式,避免PerformanceCounter的潜在问题
        using (var pc = new PerformanceCounter("Memory", "Available Bytes"))
        {
            long availableBytes = pc.NextValue();
            // 取可用内存的70%作为分区大小,留更多冗余避免内存溢出
            long partitionSize = (long)(availableBytes * 0.7);
            // 确保分区大小至少为1MB,避免过小分区导致循环次数过多
            return Math.Max(Math.Min(partitionSize, totalFileSize), 1024 * 1024);
        }
    }

    public void Execute(int sizeInGb)
    {
        try
        {
            long totalBytesToWrite = (long)sizeInGb * 1024 * 1024 * 1024;
            string filePath = Path.Combine(_fileLocation, $"file_{sizeInGb}-GB.txt");

            // 确保目录存在
            Directory.CreateDirectory(_fileLocation);

            using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Create, "mmf", totalBytesToWrite))
            {
                long partitionSize = GetPartitionSize(totalBytesToWrite);
                long offset = 0;

                while (offset < totalBytesToWrite)
                {
                    // 计算当前分区的实际大小(最后一个分区可能小于设定的partitionSize)
                    long currentPartitionSize = Math.Min(partitionSize, totalBytesToWrite - offset);

                    using (var accessor = mmf.CreateViewAccessor(offset, currentPartitionSize))
                    {
                        RandomLinesGenerator.WriteRandomBytesToAccessor(accessor, currentPartitionSize, 10, 2500);
                        offset += currentPartitionSize;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            // 这里可以添加日志或错误处理逻辑
            Console.WriteLine($"错误: {ex.Message}");
        }
    }
}

关键说明

  1. 直接写入内存映射:WriteRandomBytesToAccessor方法直接向MemoryMappedViewAccessor写入数据,不再预存全量数据到内存,彻底解决内存溢出问题
  2. 内存效率优化:使用Span<byte>和栈分配缓冲区减少堆内存分配,复用Random实例提升性能和随机序列质量
  3. 分区逻辑修正:用循环处理剩余不足一个分区的部分,避免遗漏数据
  4. 内存计算调整:将分区大小设为可用内存的70%,保留更多系统冗余,同时确保最小分区为1MB,避免过多循环

内容的提问来源于stack exchange,提问作者Usman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:38:10