【问题标题】:String VS Byte[], memory usageString VS Byte[],内存使用情况
【发布时间】:2015-10-26 16:28:40
【问题描述】:

我有一个使用大量字符串的应用程序。所以我有一些内存使用问题。 我知道在这种情况下最好的解决方案之一是使用数据库,但我暂时不能使用它,所以我正在寻找其他解决方案。

在 C# 中,字符串存储在 Utf16 中,这意味着与 Utf8 相比,我损失了一半的内存使用量(对于我的字符串的主要部分)。 所以我决定使用 utf8 字符串的字节数组。但令我惊讶的是,这个解决方案比我的应用程序中的简单字符串占用的内存空间多两倍。

所以我做了一些简单的测试,但我想知道专家的意见以确保。

测试 1:固定长度字符串分配

var stringArray = new string[10000];
var byteArray = new byte[10000][];
var Sb = new StringBuilder();
var utf8 = Encoding.UTF8;
var stringGen = new Random(561651);
for (int i = 0; i < 10000; i++) {
    for (int j = 0; j < 10000; j++) {
        Sb.Append((stringGen.Next(90)+32).ToString());
    }
    stringArray[i] = Sb.ToString();
    byteArray[i] = utf8.GetBytes(Sb.ToString());
    Sb.Clear();
}
GC.Collect();
GC.WaitForFullGCComplete(5000);

内存使用

00007ffac200a510        1        80032 System.Byte[][]
00007ffac1fd02b8       56       152400 System.Object[]
000000bf7655fcf0      303      3933750      Free
00007ffac1fd5738    10004    224695091 System.Byte[]
00007ffac1fcfc40    10476    449178396 System.String

正如我们所见,字节数组占用的内存空间减少了两倍,这并不奇怪。

测试 2:随机大小的字符串分配(具有实际长度)

var stringArray = new string[10000];
var byteArray = new byte[10000][];
var Sb = new StringBuilder();
var utf8 = Encoding.UTF8;
var lengthGen = new Random(2138784);
for (int i = 0; i < 10000; i++) {
    for (int j = 0; j < lengthGen.Next(100); j++) {
        Sb.Append(i.ToString());
        stringArray[i] = Sb.ToString();
        byteArray[i] = utf8.GetBytes(Sb.ToString());
    }
    Sb.Clear();
}
GC.Collect();
GC.WaitForFullGCComplete(5000);

内存使用

00007ffac200a510        1        80032 System.Byte[][]
000000be2aa8fd40       12        82784      Free
00007ffac1fd02b8       56       152400 System.Object[]
00007ffac1fd5738     9896       682260 System.Byte[]
00007ffac1fcfc40    10368      1155110 System.String

字符串占用的空间比字节数组内存空间的两倍少一点。对于较短的字符串,我期望字符串的开销更大。 但似乎恰恰相反,为什么呢?

测试3:我的应用对应的字符串模型

var stringArray = new string[10000];
var byteArray = new byte[10000][];
var Sb = new StringBuilder();
var utf8 = Encoding.UTF8;
var lengthGen = new Random();
for (int i=0; i < 10000; i++) {
    if (i%2 == 0) {
        for (int j = 0; j < lengthGen.Next(100000); j++) {
            Sb.Append(i.ToString());
            stringArray[i] = Sb.ToString();
            byteArray[i] = utf8.GetBytes(Sb.ToString());
            Sb.Clear();
        }
    } else {
        stringArray[i] = Sb.ToString();
        byteArray[i] = utf8.GetBytes(Sb.ToString());
        Sb.Clear();
    }
}
GC.Collect();
GC.WaitForFullGCComplete(5000);

内存使用

00007ffac200a510        1        80032 System.Byte[][]
00007ffac1fd02b8       56       152400 System.Object[]
00007ffac1fcfc40     5476       198364 System.String
00007ffac1fd5738    10004       270075 System.Byte[]

这里字符串占用的内存空间比字节少得多。这可能令人惊讶,但我认为空字符串只被引用一次。是吗?但我不知道这是否能解释所有巨大的差异。难道还有别的原因吗?最好的解决方案是什么?

【问题讨论】:

    标签: c# string memory


    【解决方案1】:

    这可能令人惊讶,但我认为空字符串只被引用一次。

    是的,一个空的StringBuilder 返回string.Empty 作为其结果。下面的代码 sn -p 打印出True:

    var sb = new StringBuilder();
    Console.WriteLine(object.ReferenceEquals(sb.ToString(), string.Empty));
    

    但我不知道这是否能解释所有巨大的差异。

    是的,这完美地解释了它。您节省了 5,000 个string 对象。字节差大约是 270,000-(198,000/2),所以大约是 170 kBytes。除以 5 得到每个对象 34 个字节,这大约是 32 位系统上指针的大小。

    什么是最好的解决方案?

    做同样的事情:让自己成为一个private static readonly 空数组,并在每次从sb.ToString() 获得string.Empty 时使用它:

    private static readonly EmptyBytes = new byte[0];
    ...
    else
    {
        stringArray[i] = Sb.ToString();
        if (stringArray[i] == string.Empty) {
            byteArray[i] = EmptyBytes;
        } else {
            byteArray[i] = utf8.GetBytes(Sb.ToString());
        }
        Sb.Clear();
    }
    

    【讨论】:

    • 为什么不使用String.IsNullOrEmpty(stringArray[i])
    • @MarkJansen 这只是一个例子:我确定stringArray[i]if (i%2 == 0) 条件的else 分支中是空的,所以我可以完全跳过与string.Empty 的比较.
    • 有趣,确实使用空字节引用可以大大提高内存使用率。我忘了在我的帖子中说,我是 64 位的,单位是字节。无论如何,这并没有改变你解释的想法,即使我发现一个指针有 34 个字节也很多,(每个字符串的 26 个字节开销甚至更多)。我已经使用 System.Byte[][] 丢失了 10K 指针的大小(80032 KB,即有用内存大小的 25%)。有什么办法可以避免使用这么多间接?也许不是字节数组。
    • @Edeen 您可以通过滚动自己的内存“分配器”将更多数据压缩到相同数量的字节中,如果您的字符串永远不会改变,这很简单。本质上,您可以将所有字节预先分配在一个大块中,然后将其打包成字符串,将索引存储在下一个字符串开始的位置。如果您需要用不同长度的新字符串替换字符串,这将不起作用。
    猜你喜欢
    • 2017-09-14
    • 1970-01-01
    • 2019-07-09
    • 1970-01-01
    • 1970-01-01
    • 2017-05-06
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    相关资源
    最近更新 更多