163K网站系统官方论坛's Archiver

guanxian 发表于 2007-2-16 18:18

.net教程:在.net程序中小心使用string类型

[size=3] 在实际程序中,string类型用得非常广泛,然而,由于.net对string类型变量的独特管理方式,使用不当,会严重影响程序的性能。我们分几个方面来谈这个问题:
1 了解string数据的内存分配方式

编写一个控制台应用程序,输入以下测试代码:
    class program
    {
        static void main(string[] args)
        {
            string s = "a";
            s = "abcd";
        }
    }
使用.net framework 2.0 sdk提供的ildasm.exe工具查看生成的msil指令:
01  .method private hidebysig static void  main(string[] args) cil managed
02  {
03    .entrypoint
04    // 代码大小       14 (0xe)
05    .maxstack  1
06    .locals init ([0] string s)
07    il_0000:  nop
08    il_0001:  ldstr      "a"
09    il_0006:  stloc.0
10    il_0007:  ldstr      "abcd"
11    il_000c:  stloc.0
12    il_000d:  ret
13  } // end of method program::main
简要解释一下上述msil指令代码:
第06句给局部变量s分配一个索引号(索引号从0开始,如函数中有多个局部变量,其索引号按在函数中出现的顺序加一)。
在编译时编译器会将代码中的两个字串“a”和“abcd”写入到程序集的元数据(metadata)中,此时,这两个字串被称为“字串字面量(string literal)”。
第08句使用ldstr指令为字串对象“a”分配内存,并将此对象引用压入到线程堆栈中。
第09句使用stloc指令从线程堆栈顶弹出先前压入的对象引用,将其传给局部变量s(其索引号为0)。
同样的过程对“abcd”重复进行一次,所以这两句简单的代码
            string s = "a";
            s = "abcd";
将会导致clr使用ldstr指令分配两次内存。
根据上述分析,读者一定明白了string变量的内容是只读的,给其赋不同的值将会导致内存的重新分配。因此,为提高程序性能,编程时应尽量减少内存的分配操作。
下面对代码中常见的字串用法进行分析,从中读者可以知道如何避免严重影响程序性能的字串操作。
2 尽量少使用字串加法运算符

请看以下两段代码:
  (1)          string s1 = "ab";
                     s1+="cd";
  (2)          string s1="ab"+"cd";
这两段代码运行结果一样,但速度一样快吗?
请看第(1)段代码生成的msil指令:
  .locals init ([0] string s1)
  il_0000:  nop
  il_0001:  ldstr      "ab"
  il_0006:  stloc.0
  il_0007:  ldloc.0
  il_0008:  ldstr      "cd"
  il_000d:  call       string [mscorlib]system.string::concat(string,
                                                              string)
  il_0012:  stloc.0
  il_0013:  ret
再看第(2)段代码生成的指令:
.locals init ([0] string s1)
  il_0000:  nop
  il_0001:  ldstr      "abcd"
  il_0006:  stloc.0
  il_0007:  ret
可以很清楚地看到,第(1)段代码将导致string类的concat()方法被调用(实现字串加法运算)。对于第(2)段代码,由于c#编译器聪明地在编译时直接将两个字串合并为一个字串字面量,所以程序运行时clr只调用一次ldstr指令就完成了所有工作,其执行速度谁快就不言而喻了!

3 避免使用加法运算符连接不同类型的数据
  
请看以下代码:
string str = "100+100=" + 200;
     console.writeline(str);
生成的msil指令为:
  .maxstack  2
  .locals init ([0] string str)
  il_0000:  nop
  il_0001:  ldstr      "100+100="
  il_0006:  ldc.i4     0xc8
  il_000b:  box        [mscorlib]system.int32
  il_0010:  call       string [mscorlib]system.string::concat(object,
                                                              object)
  il_0015:  stloc.0
  il_0016:  ldloc.0
  il_0017:  call       void [mscorlib]system.console::writeline(string)
  il_001c:  nop
  il_001d:  ret
可以清晰地看到,这两句c#代码不仅导致了string类的concat()方法被调用(il_0010),而且还引发了装箱操作(il_000b)!
concat()方法会导致clr为新字串分配内存空间,而装箱操作不仅要分配内存,还需要创建一个匿名对象,对象创建之后还必须有一个数据复制的过程,代价不菲!
改为以下代码:
            string str = "100+100=";
            console.write(str);
            console.writeline(200);
生成的msil指令为:
  .maxstack  1
  .locals init ([0] string str)
  il_0000:  nop
  il_0001:  ldstr      "100+100="
  il_0006:  stloc.0
  il_0007:  ldloc.0
  il_0008:  call       void [mscorlib]system.console::write(string)
  il_000d:  nop
  il_000e:  ldc.i4     0xc8
  il_0013:  call       void [mscorlib]system.console::writeline(int32)
  il_0018:  nop
  il_0019:  ret
可以看到,虽然多了一次方法调用(console.write)方法,但却避免了复杂的装箱操作,也避免了调用string.concat()方法对内存的频繁分配操作,性能更好。

4.在循环中使用stringbuilder代替string实现字串连接

在某些场合需要动态地将多个子串连接成一个大字串,比如许多复杂的sql命令都是通过循环语句生成的。这时,应避免使用string类的加法运算符,举个简单的实例:
            string str ="";
            for (int i = 1; i <= 10; i++)
            {
                str += i;
                if(i<10)
                    str += "+";
            }
上述代码将生成一个字串:1+2+…+10。
有了前面的知识,读者一定知道这将导致进行10次装箱操作,19次字串内存分配操作(由string.concat()方法引发),由于生成的msil指令太长,此处不再列出,请读者自行用ildasm.exe工具查看上述代码生成的msil指令。
改为以下代码,程序性能会好很多:
           //预先分配1k的内存空间
            stringbuilder sb = new stringbuilder(1024);
            for (int i = 1; i <= 10; i++)

            {
                sb.append(i);
                if(i<10)
                    sb.append("+");
            }
            string result = sb.tostring();
通过使用ildasm.exe工具查看生成的msil代码,发现虽然上述代码生成的msil指令比前面多了7条,但却避免了耗时的装箱操作,而且内存分配的次数也少了很多。当循环的次数很大时,两段代码的运行性能差异很大。





[/size]

页: [1]

Powered by Discuz! Archiver 6.1.0  © 2001-2007 Comsenz Inc.