cocos转unity笔记(二)C#的ArrayList和List性能对比

接上篇,发现一个神奇的现象,List<object>的存储速度比List<T>的速度要快

性能测试准备

既然要做性能测试,那我们肯定需要一个工具来统计代码块运行时间。C#里有现成的时间函数供我们使用,一般都是这么写:

1
2
3
4
5
var time1 = System.DateTime.Now;
// 会花掉很多时间的代码
var time2 = System.DateTime.Now;
var diff = time2.Subtract(time1);
Console.WriteLine("Time Cost: {0}ms", diff.TotalMilliseconds);

有点儿麻烦,那我封装一下好了,成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class TimeCount
{
private DateTime m_startTime;
private bool m_started;

public TimeCount()
{
m_started = false;
m_startTime = System.DateTime.Now;
}

public void start()
{
m_started = true;
m_startTime = System.DateTime.Now;
}

public double stop()
{
if (!m_started)
{
return 0;
}
TimeSpan diff = System.DateTime.Now.Subtract(m_startTime);
double ret = diff.TotalMilliseconds;
return ret;
}
};

直接调用Start和End就行

ArrayListList<object>

现在网上所有的说法都不赞同使用ArrayList这个非泛型容器。理由是:

  • 效率低下,涉及基本类型的保存时会产生装箱拆箱的操作,损失很大一部分性能
  • 就算有时候我们有需要List<object>这样的容器,但是还是尽量写成List<object>,写成ArrayList会和其他List显得不整齐

是这样没错,尤其是第二条,所以我们直接抛弃掉ArrayList的写法实际上,ArrayListList<object>的效率是一样的,用如下代码测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
System.Collections.ArrayList l0 = new System.Collections.ArrayList();
tc.start();
for (var i = 0; i < 1000000; ++i)
{
ClassData item = new ClassData();
item.intValue = i;
l0.Add(item);
}
Console.WriteLine("Time Cost: {0}ms -- create l0", tc.stop());

List<object> l1 = new List<object>();
tc.start();
for (var i = 0; i < 1000000; ++i)
{
ClassData item = new ClassData();
item.intValue = i;
l1.Add(item);
}
Console.WriteLine("Time Cost: {0}ms -- create l1", tc.stop());

输出结果如下:

pic1

于是我们就不再考虑ArrayList了

List<object>List<T>

这里的T是我们自定义的一个结构体或类

至于这里到底是结构体还是类,测试结果相差很远。

结构体

先看结构体的情况:

1
2
3
4
struct StructData
{
public int intValue;
};

接下来是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<int> l3 = new List<int>();
tc.start();
for (var i = 0; i < 1000000; ++i)
{
l3.Add(i);
}
Console.WriteLine("Time Cost: {0}ms -- create l3", tc.stop());

List<object> l4 = new List<object>();
tc.start();
for (var i = 0; i < 1000000; ++i)
{
l4.Add(i);
}
Console.WriteLine("Time Cost: {0}ms -- create l4", tc.stop());

多执行几次,运行结果如下:

pic2

可以看到,当我们打算用List<T>存储结构体时,最好不要用List<object>,因为在存储的时候必须进行装箱的操作,也就是把存储于栈的结构体保存到堆中,这会极大地损耗性能

如果T不是结构体而是一个我们手动new出来的对象,那么理论上就不存在装箱和拆箱的操作了,然后再试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 类的声明
class ClassData
{
public int intValue;
};

// 测试代码
List<object> l1 = new List<object>();
tc.start();
for (var i = 0; i < 1000000; ++i)
{
ClassData item = new ClassData();
item.intValue = i;
l1.Add(item);
}
Console.WriteLine("Time Cost: {0}ms -- create l1", tc.stop());

List<ClassData> l2 = new List<ClassData>();
tc.start();
for (var i = 0; i < 1000000; ++i)
{
ClassData item = new ClassData();
item.intValue = i;
l2.Add(item);
}
Console.WriteLine("Time Cost: {0}ms -- create l2", tc.stop());

同样多执行几次,运行结果如下:

pic3

咦,List<MyClass>的速度居然还不如List<object>

我也不太清楚为啥会这样,估计是List<MyClass>存的时候需要进行类型检查,这一步消耗了一定的时间吧

总结

其实这一篇也没啥实际意义,在实际的应用中还是尽量用List<MyClass>吧,既不用考虑装箱导致的性能损耗,又能保证类型安全,损失的部分性能也是可以接受的,除非某些对应能特别敏感的需求。

PS. 新的发现,在List<object>List<T>的对比中,使用macOS下的monoDevelop产生的结果会比较整齐,在Windows10下使用VS2015产生的结果会比较不整齐,统计出来的时间起伏较大

下一篇开始正儿八经做游戏啦