52

我想知道特定于 .Net 框架的字符串实习的过程和内部结构。还想知道使用实习的好处以及我们应该使用字符串实习来提高性能的场景/情况。虽然我从 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;
}
4

5 回答 5

38

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

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

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

于 2011-11-08T17:23:15.013 回答
24

实习是一个内部实现细节与拳击不同,我认为了解比您在里希特的书中读到的更多内容没有任何好处。

手动实习字符串的微优化好处是最小的,因此通常不推荐。

这可能描述了它:

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
    }
}
于 2011-11-08T17:22:38.423 回答
23

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

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

就我而言,我在静态字典中实习了另一种类型的对象,因为它们经常被重用,这在将它们持久化到磁盘之前充当了快速缓存。

这些对象中的大多数字段都是字符串,并且值池相当小(无论如何,远小于实例的数量)。

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

因此,实习对象大大减少了内存使用量,在实习期间实习他们的字符串值也是如此。

于 2013-09-13T06:25:32.443 回答
17

内部字符串具有以下特点:

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

这些特征的后果是:

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

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

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

  • 如果你有大量的 inned 字符串,string interning 会变得很慢,并且线程在访问 interned string 字典时会互相阻塞。

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

  1. 您正在实习的字符串集相当小。
  2. 每次实习时,您都会多次比较这些字符串。
  3. 您真的很关心分钟的性能优化。
  4. 您没有很多线程积极地实习字符串。
于 2017-06-26T01:11:03.083 回答
13

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

例如,如果您读取字符串并将其保存在列表中以进行缓存;并且完全相同的字符串出现 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;
}

内部化还提高了相等比较的性能。实习生版本下面的示例大约需要 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);
}
于 2017-03-02T20:04:15.650 回答