在.NET中绕过2 GB的集合限制

时间:2014-11-16 04:47:19

标签: c# .net list exception collections

From this question,我认为通过使用以下模式创建BigList数据类型可以绕过2 GB的集合大小限制(顺便说一下,如果你在x86应用程序上默认强加了这个限制,对尝试它感到好奇):

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RegistryHawk
{
    class Program
    {

        struct RegistryPath
        {
            public RegistryView View;
            public string Path;
            public bool IsKey;
            public RegistryValueKind ValueKind;
            public string ValueName;
            public object Value;
            public int HashValue;
        }

        public class BigList<T>
        {
            object listLock = new object();
            List<List<T>> Items = new List<List<T>>();
            int PageSize = 1000000; // Tweak this to be the maximum size you can grow each individual list before reaching the 2 GB size limit of .NET.
            public ulong Count = 0;
            int listCount = 0;

            public BigList()
            {
                Items.Add(new List<T>());
            }

            public void Add(T item)
            {
                lock (listLock)
                {
                    if (Items[listCount].Count == PageSize)
                    {
                        Items.Add(new List<T>());
                        listCount++;
                    }
                    Items[listCount].Add(item);
                    Count++;
                }
            }
        }

        static void Main(string[] args)
        {
            BigList<RegistryPath> snapshotOne = new BigList<RegistryPath>();
            WalkTheRegistryAndPopulateTheSnapshot(snapshotOne);
            BigList<RegistryPath> snapshotTwo = new BigList<RegistryPath>();
            WalkTheRegistryAndPopulateTheSnapshot(snapshotTwo);
        }

        private static void WalkTheRegistryAndPopulateTheSnapshot(BigList<RegistryPath> snapshot)
        {
            List<ManualResetEvent> handles = new List<ManualResetEvent>();
            foreach (RegistryHive hive in Enum.GetValues(typeof(RegistryHive)))
            {
                foreach (RegistryView view in Enum.GetValues(typeof(RegistryView)).Cast<RegistryView>().ToList().Where(x => x != RegistryView.Default))
                {
                    ManualResetEvent manualResetEvent = new ManualResetEvent(false);
                    handles.Add(manualResetEvent);
                    new Thread(() =>
                    {
                        WalkKey(snapshot, view, RegistryKey.OpenBaseKey(hive, view));
                        manualResetEvent.Set();
                    }).Start();
                }
            }
            ManualResetEvent.WaitAll(handles.ToArray());
        }

        private static void WalkKey(BigList<RegistryPath> snapshot, RegistryView view, RegistryKey key)
        {
            RegistryPath path = new RegistryPath { View = view, Path = key.Name, HashValue = (view.GetHashCode() ^ key.Name.GetHashCode()).GetHashCode() };
            snapshot.Add(path);
            string[] valueNames = null;
            try
            {
                valueNames = key.GetValueNames();
            }
            catch { }
            if (valueNames != null)
            {
                foreach (string valueName in valueNames)
                {
                    RegistryValueKind valueKind = RegistryValueKind.Unknown;
                    try
                    {
                        valueKind = key.GetValueKind(valueName);
                    }
                    catch { }
                    object value = key.GetValue(valueName);
                    RegistryPath pathForValue = new RegistryPath { View = view, Path = key.Name, ValueKind = valueKind, ValueName = valueName, Value = value, HashValue = (view.GetHashCode() ^ key.Name.GetHashCode() ^ valueKind.GetHashCode() ^ valueName.GetHashCode()).GetHashCode() };
                    snapshot.Add(pathForValue);
                }
            }
            string[] subKeyNames = null;
            try
            {
                subKeyNames = key.GetSubKeyNames();
            }
            catch { }
            if (subKeyNames != null)
            {
                foreach (string subKeyName in subKeyNames)
                {
                    try
                    {
                        WalkKey(snapshot, view, key.OpenSubKey(subKeyName));
                    }
                    catch { }
                }
            }
        }
    }
}

但是,CLR仍会触发System.OutOfMemory异常。它不会被抛出任何地方,但我看到程序执行完全停止在2 GB左右的RAM,当我在Visual Studio中冻结我的代码时,它表明每当我尝试查看内部变量的状态时抛出了内存不足异常应用程序的任何线程。它永远不会在第一次调用WalkTheRegistryAndPopulateTheSnapshot(snapshotOne);时发生,但是当第二次调用WalkTheRegistryAndPopulateTheSnapshot(snapshotTwo);时,它会在我的集合中以大约2 GB的整体RAM使用量停止执行程序。整个代码都已发布,因此如果您有一个强大的注册表,您可能会看到它在x86控制台应用程序上生成。有没有我在这里没有掌握的东西,或者这种模式不是解决Stack上的另一个问题似乎可以达到的2 GB集合大小限制的有效方法?

1 个答案:

答案 0 :(得分:3)

我将扩展我的评论。如果您正在编写32位应用程序,则在处理大量数据时会遇到严重的内存限制。

最重要的是要记住32位应用程序限制为绝对最大值为2 ^ 32字节(4 GB)的内存。实际上,它通常是2 GB,如果你有那么多内存并且应用程序可以识别大地址,那么它可能是3 GB。

还有.NET强加的2 GB限制,它将任何单个对象的大小限制为不超过2 GB。您很少会在32位程序中遇到此限制,原因很简单,因为即使在内存超过2 GB的计算机上,也不太可能存在大小为2 GB的连续内存块。 / p>

在64位版本的.NET中也存在2 GB限制,除非您运行的是.NET 4.5并使用启用大对象的app.config设置。

至于为什么像BigList这样的东西存在于32位版本中,它是一种绕过需要连续内存块的方法。例如,一个包含2.5亿个项目的List<int>需要一个千兆字节:一个大小为1 GB的连续内存块。但是如果使用BigList技巧(就像在代码中那样),则需要250个单独的内存块,大小为4 MB。与单个1 GB块相比,你有更多可能拥有250块4 MB的块。