.Net Framework中的字符串实习 - 有什么好处以及何时使用实习

时间:2011-11-08 17:19:44

标签: c# .net string performance string-interning

我想知道特定于.Net framework 的字符串实习的过程和内部。还想知道使用实习的好处以及我们应该使用字符串实习来提高性能的场景/情况。虽然我已经从Jeffery Richter的CLR书中学习实习,但我仍然感到困惑,并希望更详细地了解它。

[编辑]使用如下示例代码询问特定问题:

private void MethodA()
{
    string s = "String"; // line 1 - interned literal as explained in the answer        

    //s.intern(); // line 2 - what would happen in line 3 if we uncomment this line, will it make any difference?
}

private bool MethodB(string compareThis)
{
    if (compareThis == "String") // line 3 - will this line use interning (with and without uncommenting line 2 above)?
    {
        return true;
    }
    return false;
}

5 个答案:

答案 0 :(得分:29)

通常,当您使用文字字符串值时,实习会自动发生。实习提供的好处是,只要在文件内存中使用一个副本,无论它使用的频率如何。

话虽如此,很少有理由在运行时生成自己的字符串,或者甚至考虑正常开发的字符串实习。

如果您要对可能相同的运行时生成的字符串进行比较,那么可能会有很多好处(因为实习可以通过ReferenceEquals加速比较)。但是,这是一种高度专业化的用法,需要进行大量的分析和测试,除非存在测量问题,否则我不会考虑进行优化。

答案 1 :(得分:19)

实习是内部实施细节与拳击不同,我不认为知道比在Richter的书中读到的更多。

手动实习字符串的微优化优势最小因此通常不建议使用。

这可能描述了它:

class Program
{
    const string SomeString = "Some String"; // gets interned

    static void Main(string[] args)
    {
        var s1 = SomeString; // use interned string
        var s2 = SomeString; // use interned string
        var s = "String";
        var s3 = "Some " + s; // no interning 

        Console.WriteLine(s1 == s2); // uses interning comparison
        Console.WriteLine(s1 == s3); // do NOT use interning comparison
    }
}

答案 2 :(得分:18)

这是一个“老”问题,但我对此有不同的看法。

如果您将从小池中获得大量长期字符串,则实习可以提高内存效率。

在我的情况下,我在静态字典中实习了另一种类型的对象,因为它们经常被重复使用,并且在将它们保存到磁盘之前用作快速缓存。

这些对象中的大多数字段都是字符串,值池非常小(无论如何都比实例数小得多)。

如果这些是瞬态对象,那就没关系,因为字符串字段会经常被垃圾收集。但由于对它们的引用被持有,它们的内存使用量开始累积(即使没有添加新的唯一值)。

因此实际上,对象大大减少了内存使用量,并且在实际执行时也会将字符串值实际化。

答案 3 :(得分:8)

字符串的内部化会影响内存消耗。

例如,如果您读取字符串并将其保留在列表中以进行缓存;并且完全相同的字符串出现10次,如果使用string.Intern,则字符串实际上只存储在内存中一次。如果不是,则将该字符串存储10次。

在下面的示例中,string.Intern变体消耗大约44 MB,而无版本(未注释)消耗1195 MB。

static void Main(string[] args)
{
    var list = new List<string>();

    for (int i = 0; i < 5 * 1000 * 1000; i++)
    {
        var s = ReadFromDb();
        list.Add(string.Intern(s));
        //list.Add(s);
    }

    Console.WriteLine(Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024 + " MB");
}

private static string ReadFromDb()
{
    return "abcdefghijklmnopqrstuvyxz0123456789abcdefghijklmnopqrstuvyxz0123456789abcdefghijklmnopqrstuvyxz0123456789" + 1;
}

内化也提高了equals-compare的性能。实习生版本下面的示例大约需要1个时间单位,而非实习生需要7个时间单位。

static void Main(string[] args)
{
    var a = string.Intern(ReadFromDb());
    var b = string.Intern(ReadFromDb());
    //var a = ReadFromDb();
    //var b = ReadFromDb();

    int equals = 0;
    var stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < 250 * 1000 * 1000; i++)
    {
        if (a == b) equals++;
    }
    stopwatch.Stop();

    Console.WriteLine(stopwatch.Elapsed + ", equals: " + equals);
}

答案 4 :(得分:5)

Interned字符串具有以下特征:

  • 两个相同的字符串将在内存中具有相同的地址。
  • 在您的应用程序终止之前,不会释放被占用字符串占用的内存。
  • 实习字符串涉及计算哈希值并在消耗CPU周期的字典中查找。
  • 如果多个线程同时实习字符串会相互阻塞,因为对连字符串字典的访问会被序列化。

这些特征的后果是:

  • 您可以通过比较地址指针来测试两个内部字符串是否相等,这比比较字符串中的每个字符快得多。如果字符串很长并且以相同的字符开头,则尤其如此。您可以使用Object.ReferenceEquals方法比较实习字符串,但使用string ==运算符会更安全,因为它会检查字符串是否是互联网优先。

  • 如果在应用程序中多次使用相同的字符串,则应用程序只会在内存中存储一​​个字符串副本,从而减少运行应用程序所需的内存。

  • 如果您实习许多不同的字符串,这将为那些永远不会被释放的字符串分配内存,并且您的应用程序将消耗不断增加的内存量。

  • 如果您有大量的实习字符串,字符串实习可能会变慢,并且线程会在访问实习字符串字典时相互阻塞。

只有在以下情况下才应使用字符串实习:

  1. 您实习的字符串集非常小。
  2. 每次实习时,您都会多次比较这些字符串。
  3. 你真的关心微小的性能优化。
  4. 你没有多少线程积极地实施字符串。