01 C#中i++与++i性能比较
经人提醒,得知二者性能是不一样的,在C++中一般写++i能获得更好的性能,或许后来的编译器有了更好的优化使得二者性能一样了。但是C#中,二者性能有无区别呢?真要看二者有无区别,就去比较IL,比如这个网站就能在线看所写代码IL:
比如我的测试:
using System;
public class C
{
public void M()
{
int i = 0;
i++;
}
public void N()
{
int i = 0;
++i;
}
public void T()
{
int i = 0;
i += 1;
}
public void P()
{
int i = 0;
i = i + 1;
}
}
生成的IL是一样的:
// Methods
.method public hidebysig
instance void M () cil managed
{
// Method begins at RVA 0x2050
// Code size 8 (0x8)
.maxstack 2
.locals init (
[0] int32
)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldc.i4.1
IL_0005: add
IL_0006: stloc.0
IL_0007: ret
} // end of method C::M
.method public hidebysig
instance void N () cil managed
{
// Method begins at RVA 0x2064
// Code size 8 (0x8)
.maxstack 2
.locals init (
[0] int32
)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldc.i4.1
IL_0005: add
IL_0006: stloc.0
IL_0007: ret
} // end of method C::N
另外我用循环测试了,也是一样的性能:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Diagnostics;
public class TestIPlus : MonoBehaviour
{
void Start ()
{
Stopwatch sw = new Stopwatch();
sw.Start();
int i = 0;
while(i < 100000000)
{
++i;
}
sw.Stop();
UnityEngine.Debug.Log(sw.Elapsed);
sw.Restart();
i = 0;
while (i < 100000000)
{
i++;
}
sw.Stop();
UnityEngine.Debug.Log(sw.Elapsed);
}
void Update ()
{
}
}
如下:
所以二者是一样的性能表现,不用纠结。或许C++编译器对二者编译后得到的代码不一样,但是C#中则是一模一样的。
下面补充测试C++的:
#include "pch.h"
#include <iostream>
#include <time.h>
using namespace std;
int main()
{
clock_t startTime, endTime;
startTime = clock();
int i = 0;
while (i < 100000000)
{
++i;
}
endTime = clock();
cout << (double)(endTime - startTime) / CLOCKS_PER_SEC << endl;
startTime = clock();
i = 0;
while (i < 100000000)
{
i++;
}
endTime = clock();
cout << (double)(endTime - startTime) / CLOCKS_PER_SEC << endl;
}
得到的结果如下:
二者表现是一样的,就和网上查到的资料差不多,老旧的编译器可能对i++不友好,但是新的都有优化了,二者是一样的。我是习惯上写i++,感觉代码结构上比较一致,现在也已经验证二者没有性能差异,就继续用i++。
另外,单就这一亿次自增来说,C#和C++性能表现也是一样的。
02 C#中置为为GC而设置对象为null
用C#写的代码,有人会在dispose方法后将该对象设置为null,或者一个list执行了clear后再置为null,目的是想要及时让GC发现无用的资源以回收释放,降低内存占用。或者有时候设置程序运行一段时间自动执行一句GC方法来主动进行垃圾回收,这是我这一节要研究的。我对不熟悉的做法保持谨慎的态度,尽量不要人云亦云,就如同上面关于i++和++i的性能差别问题,要测试才知道。
首先要看一下dispose的有关知识,先看微软官方介绍:
一开始的一句话介绍就是该接口是实现未托管资源释放的一种机制:
public interface IDisposable
接着主要的是要在自己的类中实现Dispose方法:
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
而且Dispose方法不要设置为virtual,不可以被重写。
GC不能释放非托管资源,且不是实时的,因此会有性能瓶颈和不确定性问题,用IDisposable接口则可以让程序员显示调用释放非托管资源。非托管资源常见的是有关操作系统资源的对象,如文件、窗口、网络连接,数据库连接等,unity的WWW类就是这种,用完要调用Dispose。
接着回到设置对象为null的问题上,看一下StackOverflow上的回答:
https://stackoverflow.com/questions/574019/setting-an-object-to-null-vs-dispose/574659
最高票回答接近200个顶,主要说到了Dispose和GC是两码事,然后对null问题做了说明。使用using语句其实就是try/finally语法糖,保证即使发生异常的时候也能调用到Dispose,但是不是说语句块结束的时候GC就能收走该对象。Dispose是关于非托管资源的,GC是关于内存的。Finalizer也就是终结器,能够在用户忘记处理那些资源的时候进行自动清理,比如打开FileStream忘记调用Dispose或者Close方法,终结器会最终释放这些文件句柄。所以说写得好的代码是不该触发终结器的,不要忘记关闭资源。
关于null:
One small point on setting a variable to null - this is almost never required for
the sake of garbage collection. You might sometimes want to do it if it's a member
variable, although in my experience it's rare for "part" of an object to no longer
be needed.
设置变量为null对GC是没什么用的,你可能在处理对象变量的时候设置它为null,但是不再需要某个部分是很少有的情况。
接着看另一个问答:
高票回答如下:
有时候可能需要对静态变量设置null,但是总体来说是没必要为了GC而设置null的。unity的WWW使用结束用Dispose后就不必再将其设置为null 了。
最后一个:
https://blog.stephencleary.com/2010/02/q-should-i-set-variables-to-null-to.html
简单来说:
Yes, if the variable is a static field, or if you are writing an enumerable method (using yield return)
or an asynchronous method (using async and await). Otherwise, no.
This means that in regular methods (non-enumerable and non-asynchronous), you do not set
local variables, method parameters, or instance fields to null.
(Even if you’re implementing IDisposable.Dispose, you still should not set variables to null).
这里也提到了静态变量可以设置null有些用,其他的则不要这么做。
感觉还是不足,在CSDN上找了一个帖子,有好几页的讨论:
https://bbs.csdn.net/topics/300181973
总的来说,可能会有些作用但是不是很必要作用也不大吧,我觉得比较支持的观点如下:
内存回收也也没搜到许多能直接测试给出明显效果的代码,所以这个问题就靠这些说明理解下吧,一些静态变量设置null会有较大作用,别的可能并没有什么用。
我现在觉得并未做很底层,程序没有bug的话, 多占用几个字节或者多花几个毫秒并不是那么不可接受。况且话说回来,某些做法是否真的有作用也不是很清晰,这里就借用帖子中一个人的回答来结束这个问题吧:
“做v.net开发已经有好几年了,没看到加了=null对性能有明显的提高,也没看到不加=null程序也就outofmemory,对于技术,太钻牛角尖就没有意义了~~~”
03 Visual Studio C#工程添加条件编译符号
用AVPro的时候,在Unity Editor中能自动识别到内置的宏。后来想要在编译一个DLL,新建的VS工程,里面没有这些宏,所以许多代码编译的时候就被丢弃了。这时候就可以设置条件编译符号:
在项目属性上找到“生成”标签,配置一些条件编译符合,如“UNITY_EDITOR_WIN;UNITY_STANDALONE_WIN”,就类似于unity Player Settings的Scripting Define Symbols一样:
这样那些灰色的代码就亮起来了:
而且编译得到的DLL中就会有这些代码,否则你觉得编译没问题,也能调用到,但实际里面许多代码都空了。