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

VS1*_*VS1 40 .net c# string performance string-interning

我想知道特定于.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;
}
Run Code Online (Sandbox Code Playgroud)

Ree*_*sey 30

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

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

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

  • 您错过了其他一些答案中讨论的另一个重要用例。如果您要存储具有许多相同字符串的真正大量数据,则可以节省大量内存。当我需要加载包含许多重复字符串的非常大(数千兆字节)的数据文件并将其保留在内存中时,这对我来说是一个救星。 (2认同)

Ali*_*tad 20

实习是一个内部实现细节.与拳击不同,我认为在了解 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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考 - 您的"无实习"行仍将使用两个实习字符串来生成非实习字符串.此外,字符串的比较总是使用相同的比较(没有"实习比较"或"其他比较") - 但是有一个短路检测成员是否指向同一个实例. (19认同)
  • 对实施细节视而不见是一件坏事.考虑到由于缺乏字符串实习,许多人目前正在使用解决方法.知道它存在以及它可以改善代码性能的位置实际上可能允许您删除已经存在的"微优化",这些都是为了提高可读性而交换性能的.编辑:我想有两个关于实现细节的思想流派,但是很多人会认为一个优秀的程序员的知识尽可能地落在堆栈中,尤其是编译器的特性. (9认同)

har*_*rpo 20

这是一个"老"的问题,但我对它有不同的看法.

如果你将从一个小池中获得许多长寿命的字符串,实习可以提高内存效率.

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

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

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

因此实际上,对象实际上减少了内存使用量,因此在实际执行时它们的字符串值实际上也是如此.


J. *_*sen 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;
}
Run Code Online (Sandbox Code Playgroud)

内化还可以提高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);
}
Run Code Online (Sandbox Code Playgroud)


bik*_*868 5

实习字符串具有以下特征:

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

这些特征的后果是:

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

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

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

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

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

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