Unity与C#学习记录 - 12

14 篇文章 0 订阅

01 C#中i++与++i性能比较

经人提醒,得知二者性能是不一样的,在C++中一般写++i能获得更好的性能,或许后来的编译器有了更好的优化使得二者性能一样了。但是C#中,二者性能有无区别呢?真要看二者有无区别,就去比较IL,比如这个网站就能在线看所写代码IL:

https://sharplab.io

比如我的测试:

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的有关知识,先看微软官方介绍:

https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?redirectedfrom=MSDN&view=netframework-4.7.2

一开始的一句话介绍就是该接口是实现未托管资源释放的一种机制:

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,但是不再需要某个部分是很少有的情况。

接着看另一个问答:

https://stackoverflow.com/questions/2926869/do-you-need-to-dispose-of-objects-and-set-them-to-null/2926928

高票回答如下:

有时候可能需要对静态变量设置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中就会有这些代码,否则你觉得编译没问题,也能调用到,但实际里面许多代码都空了。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值