代码改变世界

silverlight游戏设计(三)性能优化篇(外传) -- struct与class的选取

2010-12-22 17:36  姜 萌@cnblogs  阅读(1060)  评论(2编辑  收藏  举报

上篇所说打性能优化点基本上都是针对silverlight本身相关的,其实silverlight作为一个精简的跨平台.net运行时,我们同样需要注意底层的一些性能问题,比如今天所要说的struct 与 class。

说道struct和class,这就要从CLR 的类型说起。

CLR类型

underlying type:基础类型,包括int,short,long,ushort等。

Reference Type:引用类型,其实java/.net程序中大多数类型都是引用类型。对于基本类型,

Value Type:从System.ValueType继承而来,直接在栈上创建。在C++中,我们可以随意在栈上创建和销毁对象,在java中干脆就只有基本类型才能这样做。而.NET中除了基本类型以外还给予我们另一种选择,就是struct。在栈上创建的对象的好处就是速度快销毁也快,不需要等待堆上的GC老人家。

Enum:枚举。其实CLR中的枚举也是Value Type,个人认为C#中的枚举特性如可以标识数值、进行位运算等比java的enum要强很多。

其他:其实还有很多特定的类型,比如Array,interface,pointer,ByRef,TypeRef等。这些类型属于上述的几大类的特定品种,不涉及本文我就不多说了。

struct与class需要注意的地方

好了,既然struct属于ValueType,那么就有这先天的优势---在栈上分配,栈上的对象有编译器自动创建和销毁,还有一点,对于数组其自身是引用类型,因此无论包含的是结构体还是类对象的引用,所有数组都通过堆分配内存。然而,一次性分配结构体数组的所有内存,使得使用结构体仍然有一些优点。结构体数组中的所有内存都是连续的,因此相比于堆中分散的内存,程序可以更加快速地访问结构体数组中的元素。总而言之在栈上创建对象/销毁对象的速度是非常快的,但是栈资源有限,不适合处理复杂的大的逻辑,所以结构体处理小对象,类处理复杂的打对象逻辑;类创建一个新对象,只是创建了一个引用,新对象的修改同时也会反映到源对象上,而结构体创建一个新对象后是创造源对象的一个副本,新对象的任何修改不会反应到源对象上,所以栈并非万金油,对于复杂生命周期的对象在栈上很难管理,而专由GC负责的堆则正好适用管理复杂对象的生命周期。

还有一点需要注意:值对象都是值传递,类对象都是引用传递,这就意味着值对象在传参、赋值过程中会消耗更多的内存,这也是我们需要合理使用struct的因素。

测试

下面我们来做个测试,看看struct和class在性能上的差异。

我们从三个方面考察:

1.对象创建速度

2.小对象值传递与引用传递速度对比

测试代码如下

测试代码1
static void vs1()
        {
            
int nLoop = 20000 * 20000;
            var sw 
= new Stopwatch();
            Console.WriteLine(
"对象创建速度对比...");
            sw.Start();
            
for (int i = 0; i < nLoop; i++)
            {
                var o 
= new A1();
                o.a 
= 10;
                o.b 
= 15;
                o.c 
= "b";
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            sw.Restart();
            
for (int i = 0; i < nLoop; i++)
            {
                var o 
= new A2();
                o.a 
= 10;
                o.b 
= 15;
                o.c 
= "b";
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
        }

 

 

测试代码2
static void vs2()
        {
            
int nLoop = 20000 * 20000;
            var sw 
= new Stopwatch();
            Console.WriteLine(
"小对象值传递和引用对比...");
            sw.Start();
            
for (int i = 0; i < nLoop; i++)
            {
                var o 
= new A1();
                test1(o);
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
            sw.Restart();
            
for (int i = 0; i < nLoop; i++)
            {
                var o 
= new A2();
                test1(o);
            }
            sw.Stop();
            Console.WriteLine(sw.Elapsed);
        }

 

 

设计的结构够类
struct A1
    {
        
public int a;
        
public int b;
        
public string c;
    }
    
class A2
    {
        
public int a;
        
public int b;
        
public string c;
    }
    
struct B1
    {
        
public A1 a1;
    }
    
class B2
    {
        
public A2 a2;
    }
    
struct C1
    {

        
public byte[] largeObj1;
        
public string[] largeObj2;
    }
    
class C2
    {
        
public byte[] largeObj1;
        
public string[] largeObj2;
    }

 

 

 

第一段测试代码对象的创建速度,第二段测试代码对比参数传递。

B2(V)1IIIQX3YH35D4TOH_C

从第一段测试结果中可以看出栈上对象创建的明显性能优势,创建同样数量的对象,且A1与A2都只含有3个字段,结果是3秒 vs 15秒。

第二段测试结果虽然是5秒vs12秒,但如果减去对象创建上的差异后其实传递上值对象性能是属于引用传递的。

所以我们得出结论:在栈上创建对象/销毁对象的速度是非常快的,但是栈资源有限,不适合处理复杂的大的逻辑,所以结构体处理小对象,类处理复杂的打对象逻辑。并且对于很频繁的方法参数传递要避免使用结构体。

结合实际

总体而言业务的处理过程中尽量使用类型对象,对于仅使用一次就不再需要的对象可以使用结构体。