C# 在.NETFramework中进行字符串实习-有什么好处以及何时使用实习
我想知道针对.Net framework的字符串实习的过程和内部结构。我还想知道使用实习的好处,以及我们应该使用字符串实习来提高绩效的场景/情况。虽然我从杰弗里·里克特的CLR书中学习过实习,但我仍然感到困惑,希望了解更多细节 [编辑]使用以下示例代码询问特定问题:C# 在.NETFramework中进行字符串实习-有什么好处以及何时使用实习,c#,.net,string,performance,string-interning,C#,.net,String,Performance,String Interning,我想知道针对.Net framework的字符串实习的过程和内部结构。我还想知道使用实习的好处,以及我们应该使用字符串实习来提高绩效的场景/情况。虽然我从杰弗里·里克特的CLR书中学习过实习,但我仍然感到困惑,希望了解更多细节 [编辑]使用以下示例代码询问特定问题: private void MethodA() { string s = "String"; // line 1 - interned literal as explained in the answer
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;
}
实习是一个内部实施细节。与拳击不同,我不认为知道的比你在里希特的书中读到的更多有什么好处 手动插入管柱的微观优化效益最小,因此通常不建议使用 这可能描述了它:
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
}
}
通常,当您使用文字字符串值时,实习是自动发生的。Interning提供了在内存中只保留一个文本副本的好处,无论使用频率如何 也就是说,很少有理由对自己在运行时生成的字符串进行内部调用,或者甚至考虑过为正常开发进行字符串内部调用 如果您要对可能相同的运行时生成的字符串进行大量比较,那么可能会有一些好处(因为实习可以通过ReferenceEquals加速比较)。然而,这是一个高度专业化的用法,需要大量的分析和测试,除非有一个被测量的问题,否则不会是一个优化。 < P>这是一个“老”的问题,但我有不同的角度。 如果要从一个小的池中获得大量长寿命字符串,那么实习可以提高内存效率 在我的例子中,我在一个静态字典中实习了另一种类型的对象,因为它们经常被重用,这在将它们持久化到磁盘之前充当了一个快速缓存 这些对象中的大多数字段都是字符串,值池相当小(无论如何,要比实例的数量小得多) 如果这些是临时对象,那就没关系了,因为字符串字段会经常被垃圾收集。但由于对它们的引用被保留,它们的内存使用开始累积(即使没有添加新的唯一值)
因此,对象的内部化大大减少了内存使用,在对象被内部化时,字符串值的内部化也大大减少了内存使用。字符串的内部化会影响内存消耗 例如,如果您读取字符串并将其保存在列表中以进行缓存;同一个字符串出现10次,如果使用string.Intern,该字符串实际上只在内存中存储一次。如果没有,字符串将存储10次 在下面的示例中,string.Intern变量消耗大约44MB,而不带注释的版本(未注释)消耗1195MB
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;
}
static void Main(字符串[]args)
{
var list=新列表();
对于(int i=0;i<5*1000*1000;i++)
{
var s=ReadFromDb();
添加(string.Intern);
//列表。添加(s);
}
Console.WriteLine(Process.GetCurrentProcess().privateMorysize64/1024/1024+“MB”);
}
私有静态字符串ReadFromDb()
{
返回“ABCDEFGHIJKLMNOPQRSTUYXZ0123456789ABCDEFGHIJKLMNOPQRSTUYXZ0123456789ABCDEFGHIJKLMNOPQRSTUYXZ0123456789”+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);
}
static void Main(字符串[]args)
{
var a=string.Intern(ReadFromDb());
var b=string.Intern(ReadFromDb());
//var a=ReadFromDb();
//var b=ReadFromDb();
int等于0;
var stopwatch=stopwatch.StartNew();
对于(int i=0;i<250*1000*1000;i++)
{
如果(a==b)等于++;
}
秒表;
Console.WriteLine(stopwatch.appeased+”,等于:“+equals”);
}
插入字符串具有以下特征:
- 两个相同的插入字符串在内存中具有相同的地址
- 在应用程序终止之前,不会释放被插入字符串占用的内存
- 插入一个字符串需要计算一个散列并在一个消耗CPU周期的字典中查找它
- 如果多个线程同时插入字符串,它们将互相阻止,因为对插入字符串字典的访问是序列化的
- 您可以通过比较地址指针来测试两个内部字符串是否相等,这比比较字符串中的每个字符快得多。如果字符串很长且以相同字符开头,则尤其如此。您可以将插入的字符串与
方法进行比较,但使用对象.ReferenceEquals
运算符更安全,因为它会检查字符串是否先插入string==
- 如果在应用程序中多次使用同一字符串,应用程序将只在内存中存储该字符串的一个副本,从而减少运行应用程序所需的内存
- 如果您插入许多不同的字符串,这将为那些永远不会被释放的字符串分配内存,并且您的应用程序将消耗越来越多的内存
- 如果有大量的内部字符串,则字符串内部处理可能会变慢,并且线程在访问内部字符串字典时会相互阻塞