Contents

C#效率研究

本文主要是记录研究各种C# 结构下的运行效率对比。

Linq效率测试

C#拥有Linq语句,类似与数据库操作语法。使用其中语法可以快速实现一些功能,例如排序,同时使得代码整体结构更清晰干净。

这里主要测试对几个Linq函数与通常实现的区别,并总结相关内容。Linq相关也可以参看C#语言思考部分。

对于Linq中的OrderBy来说,有两个关键点:

  • 其是一个延迟结构,即只有调用的时候才会去执行排序操作。
  • 其每次会产生一个新的容器列表。

OrderBy

而使用Sort接口则是原位置排序,所以对比操作如下:

1
2
3
4
5
6
7
8
public void TestLinqSort()
{
    // Linq对比 会返回一个新序列集合
    test_list.OrderBy((num) => num).ToList();
    // Sort对比 原位置排序 要拷贝一份
    var new_list = new List<int>(test_list);
    new_list.Sort((n1, n2) => n1.CompareTo(n2));
}
方式说明 时间花费(ms) 时间花费(ms)
数组个数:$10^3$ 数组个数:$10^7$
运行次数:$10^4$ 运行次数:1
Linq 4087 14350
Sort 1691 3934

可以看到Linq耗时还是比较高的,比copy创建List+Sort还要慢。我认为这是因为Linq不仅创建目标数组,还创建了一个中间泛型数组来做比较,称之为Sort数组。这也可以通过其接口只用一个返回Int的函数来看出,这一步相当于一部映射操作,即把对象都映射到整数上,使用整数的序关系来排序。另一方面是即便是排序操作,对于Linq来说,底层也是只有当遍历的时候才进行操作,这个可以看源码是通过一个partial quick sort来操作的。

All

Linq还有一些集合操作,可以接受一个断言函数(predict),输出集合上的对应逻辑运算的结果,例如判断全部满足的All运算。 所谓断言即输出一个true or false的函数。 显而易见的,对于Linq来说会不断调用断言函数,这种实现必定要慢一些。但是其写法更加简单,同时更加触及逻辑运算本质。此处主要记录对比会慢多少:

方式说明 时间花费(ms)
数组个数:$10^3$
运行次数:$10^7$
Linq 3863
Loop Check 391

类型转化测试

C# 作为静态类型语言。在代码层一定要带上类型结构,这就伴随着必不可少的类型转换问题。这里主要是测试一下 强转与as is等效率直接的差异而已。

运行次数:$10^9$

方式说明 时间花费(ms)
强转 2601
As转换 3065

带If判断的操作。

运行次数:$10^9$

方式说明 时间花费(ms)
as + if 4526
is + if 4319

可以看到效率差异其实都不是很大。只是强转如果类型不同会失败,但是as会返回null。而对于原本就是null类型,强转与as都会给出null。 实际对于架构来说,架构内部结构会要求传入的都是程序员知道类型来控制。所以强转也无大问题。但是从语义角度来讲实际还是有差异的,对于类型转换来说,实际上相当于调用了转换运算。也就是尝试将目标类型变成目标类型,例如一个类A和类B,如果定义了之间的转化关系,实际上也是可以操作的。从这个方面来讲,as则更有是的意思。从结构来说我们的架构,更多强调的是:我知道,它就是这个。所以比起转化操作,as更好一些。

Diction遍历测试

Dictionary遍历主要有两个方面,每次都是遍历Dictionary获取对应的KeyPair然后获取对应的值。还有一个是生成Key或者Value的迭代器来操作。这里主要对比两个方式的效率差异。

运行次数:$10^9$

方式说明 时间花费(ms)
foreach 2482
foreach +Keys 1946

可以发现单纯的遍历Keys或者Values会要快一些。这是因为虽然Keys或者Values方法虽然会生成一个中间结构来作为迭代器。但是对于遍历Dictionary实际上,会对每一个KeyValue对生成一个KeyPair对象来封装,所以会慢一点。可以参看底层数据结构部分。

IfElse测试

因为对于python等这种语言来说,对于使用?:结构与ifelse结构有很大的效率差异。在此决定测试一下这两者之间是否存在差异,测试代码对比如下。

1
2
3
4
5
6
7
8
9
public void TestIfElse()
{
    // ?:赋值 
    var result = value > 5000 ? true : false;
    // if else
    bool result = false;
    if (value > 5000)
        result = true;
}

运行次数:$10^9$

方式说明 时间花费(ms)
?: 2767
IfElse 4013

令我惊讶的是,使用?:确实要比ifelse快一点点。我怀疑可能是这种情况下,优化编译对于上一句直接优化导致。但是两边都添加一个对于resultifelse后依然是?:快一点点。 可能编译器内部存在着对?:的优化吧。

基础容器结构测试

主要是想对比看一下基础容器结构之间的新能差异。例如List与Array的foreach遍历效率差异。理论来讲,两者都是基于数组的结构,只是List由C#包装了一层。

方式说明 时间花费(ms)
数组个数:$10^3$
运行次数:$10^6$
List 16279
Array 2170