C# 循环和函数篇

  • 〇、前言
    • 系统自带计时器 -System.Diagnostics.StopWatch- 的使用
  • 一、循环类型测试
    • 1. for 循环测试
      • (1). 前后置自增自减测试
      • (2). 与 foreach 循环比较
      • (3). 循环中画蛇添足的事
        • <1> array.Length 和 arrayLength 的抉择
    • 2. while 循环测试
      • (1). 与 for 循环的比较
    • 3. do...while 循环概括
  • 二、函数测试
    • 1.申明,赋值,初始化
    • 1.无返回值无参函数测试
    • 2.传参和有返回值函数测试
    • 3.事件函数测试

〇、前言

系统自带计时器 -System.Diagnostics.StopWatch- 的使用

System.Diagnostics.StopWatch 的基本使用方法

//引用命名空间
using System.Diagnostics;//创建一个计时器
Stopwatch timer = new Stopwatch();//计时开始
timer.Start();//计时结束
timer.Stop();//转换成秒形式
decimal second = timer.Elapsed.Ticks * 0.000_000_1m;//如果要转换成微秒形式:
decimal microSecond =  timer.Elapsed.Ticks * 0.1m;//在输出面板输出代码所用时间, ":F4"是保留4位小数的意思
Debug.Log($"Takes {second:F4} second");

知识区: 微秒

一、循环类型测试

C# 提供的基本循环类型如下:

----类型---- --------------------------------作用------------------------------
for 循环 多次执行一个语句序列,简化管理循环变量的代码
while 循环 当给定条件为 true 时,重复语句或语句组。它会在执行循环主体之前测试条件。
do…while 循环 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。

资料链接: 菜鸟教程 C# 循环.

1. for 循环测试

for循环 标准格式:

for(初始化; 条件; 步进操作){ 代码块操作 }//条件满足则进入循环主体

流程图展示:

Created with Raphaël 2.3.0进入初始化满足条件?代码块操作步进操作离开循环yesno

(1). 前后置自增自减测试

本博客主用了简单的 for 循环 测试了一下自增自减的运行时间比较:

int count = 100_000
//后置自增
for(int i = 0; i < count; i++){}
//前置自增
for(int i = 0; i < count; ++i){}
//后置自减
for(int i = count - 1; i >= 0; i--){}
//前置自减
for(int i = count - 1; i >= 0; --i){}

然后在循环开始前计时,结束后记录代码运行的时长:

using System.Diagnostics;
Stopwatch timer = new Stopwatch();
timer.Start();
for(int i = 0; i < count; i++){}
timer.Stop();
Debug.Log($"Takes -{timer.Elapsed.Ticks}- ticks");

为了方便记录、观察和比较,本博客主直接将代码写在一个脚本里,并通过一次性将所有代码在较短的时间段里完成减小电脑在较大时间差的时间点上运行效率可能不一致的偶然误差,完整代码如下:

using System.Diagnostics;
using UnityEngine;
using UnityEngine.UI;//添加 Buttonpublic class TimerTest : MonoBehaviour
{public Button button;    public int count = 100_000;private void Start()=> button.onClick.AddListener(OnClicked); private void OnClicked(){for(int testIndex = -1; testIndex < 4; ++testIndex){Stopwatch timer = new Stopwatch();//因为进入 switch 的时间花费相比于1_000_000次自增自减简直微乎其微//所以就在 switch 之前开始计时timer.Start();switch(testIndex){case -1:// "预热",让电脑做好准备[doge]//因为我发现,如果不 "预热",第一个运行时间异常长与其他的               break;case 0://后置自增for(int i = 0; i < count; i++){}break;case 1://前置自增for(int i = 0; i < count; ++i){}break;case 2://后置自减for(int i = count - 1; i >= 0; i--){}break;case 3://前置自减for(int i = count - 1; i >= 0; --i){}break;}timer.Stop();UnityEngine.Debug.Log($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");}}
}

Unity里编译完成后记录实验数据

次数 后置自增 前置自增 后置自减 前置自减
1 267 269 297 267
2 270 260 260 261
3 380 348 363 368
4 348 350 347 350
5 326 327 326 331

控制台中编译完成后记录实验数据

次数 后置自增 前置自增 后置自减 前置自减
1 1258 1286 1647 1647
2 1249 1232 1647 1648
3 1261 1276 1647 1643
4 1318 1257 1646 1648
5 1275 1268 1643 1666

由此可得出结论:

  • 自增自减的前后置在循环区域里面进行的时间消耗无太大区别,但是选择自增还是自减对循环区域还是有区别的,从数据上可以看出选自减大概会多出 20%~25%个自增 的时间消耗.

前后置在对内存的操作上,i++是 使用了 i 这个值后再进行 +1,所以需要储存在一个临时的变量当中,而++i是直接 +1,没有了对内存的操作环节,相对而言++i性能是比i++更好。

(2). 与 foreach 循环比较

首先来了解一下 foreach 循环 内部的原理:

//正常的 foreach 语句
foreach (T element in elementPool)
{// foreach 循环 的代码主体部分DoSomething();
}// foreach 的后台逻辑
T a;
System.Collections.IEnumerator ienumerator = ((System.Collections.IEnumerable)elementPool).GetEnumerator();
try
{while (ienumerator.MoveNext()){a = (T)ienumerator.Current;//对应 foreach 循环 的代码主体部分DoSomething();}
}
finally
{//直观地看到 foreach 循环遍历完每个元素后会释放对应的资源System.IDisposable disposable = ienumerator as System.IDisposable;if (disposable != null) disposable.Dispose();
}

然后再通过同样的方法:

...
public int count = 100_000;
...private void OnClicked()
{//创建新的列表方便 foreach 循环遍历//创建数组也可以,这里就用列表演示List<bool> boolList = new List<bool>();for (int i = 0; i < count; ++i)boolList.Add(true);for (int testIndex = -1; testIndex < 2; ++testIndex){Stopwatch timer = new Stopwatch();timer.Start();switch (testIndex){//"预热"case -1:break;// for 循环部分case 0:for (int i = 0; i < count; ++i) {bool tempBool = boolList[i];}break;// foreach 循环部分case 1:foreach(bool tempBool in boolList){}break;}timer.Stop();UnityEngine.Debug.Log($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");}
}

编译完成后记录实验数据

次数 for 循环 foreach 循环
1 1049 2596
2 1087 2722
3 1278 3216
4 1379 3252
5 1297 3249

Unity 编译器下,相对来说在这种较少运算的实验记录中,for 循环 的运行时间快于 foreach 循环 ,不过只通过 Unity 编译器 给出的结果并不能明朗地分辨出那个性能更好。
下面这是在控制台运行的实验数据:

次数 for 循环 foreach 循环
1 3416 3033
2 3401 3032
3 3401 3041
4 3412 3035
5 3402 3035

控制台中运行,foreach 循环for循环 略快,本博客主推荐看准控制台记录的实验数据。
接下来列举出 foreach 循环for 循环 的优缺点:

  • foreach 循环

    • 优点:

      • 1.代码简洁
      • 2.不用因为数组下标上下限而纠结,特别是在上下限在外部环境下发生变化的时候
      • 3.可以一次性输出多维数组所有元素(交叉数组除外)
      • 4.循环 ArrayList 等数据集合时无需进行显式地装箱拆箱
    • 缺点:
      • 1.foreach 循环可以看做是只读循环,也就是被枚举出来的元素不能被修改
      • 2.不能对集合本身进行修改,否则会报错
      • 3.循环后会进行对应成员的垃圾回收(GC),释放使用完的资源
  • for 循环
    • 优点:

      • 1.可以通过设置上下限来控制数组的输出
      • 2.能够对数组内元素进行更改
      • 3.不仅仅可以对数组进行操作,还可以用于多次重复的计算,可用性广泛
    • 缺点:
      • 1.下文会介绍到的 C#为强类型判断 “通过下标访问数组元素的时候会多进行一次判断”

但是本博客主推荐:
尽量在遍历所有成员的情况下使用 foreach 循环,正常来说会比正常的 for循环 少 12%~13% 时间消耗
其他情况下视条件而选择

(3). 循环中画蛇添足的事

<1> array.Length 和 arrayLength 的抉择

有些时候我们会习惯性的储存变量以便后来使用。
不过在 C# 的 for 循环 中在不正确的地方使用,可能还会多此一举,例如以下的写法:

//正常循环
for (int i = 0; i < boolArray.Length; ++i) { }//储存成员,便于后来使用
int boolArrayLength = boolArray.Length;
for (int i = 0; i < boolArrayLength; ++i) { }

同样本博客主用计时器进行耗费时间上的比较:

...
public int count = 100_000;
...private void OnClicked()
{//创建新的数组,以便达到获取 Length 的目的bool[] boolArray = new bool[count];for (int i = 0; i < count; ++i)boolArray[i] = true;for (int testIndex = -1; testIndex < 5; ++testIndex){Stopwatch timer = new Stopwatch();timer.Start();switch (testIndex){//"预热"case -1:break;//编号0: 正常循环 case 0:for (int i = 0; i < count; ++i) { }break;//编号1: 每次循环都会 .Length case 1:for (int i = 0; i < boolArray.Length; ++i) { }break;//编号2: 储存了 boolArrayLength case 2:int boolArrayLength = boolArray.Length;for (int i = 0; i < boolArrayLength; ++i) { }break;//编号3: 倒着循环 case 3:for (int i = boolArray.Length - 1; i >= 0; --i) { }break;}timer.Stop();UnityEngine.Debug.Log($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");}
}

编译完成后记录实验数据

循环次数/循环编号 编号0 编号1 编号2 编号3
1 260 260 258 258
2 315 314 316 314
3 258 258 258 268
4 314 314 337 314
5 314 315 315 314

发现其实这几种方法在运行时间耗费上没有太大的区别,于是本博客主再在每个循环块中加入:

boolArray[i] = false;

并且加入指针循环片段

//编号4: 指针循环
case 4:unsafe{bool* pBoolArray = (bool*)System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(boolArray, 0);for (int i = 0; i < boolArray.Length; ++i){ pBoolArray[i] = false;}}break;

再次运行,并在编译完成后记录实验数据

循环次数/循环编号 编号0 编号1 编号2 编号3 编号4
1 889 1312 1312 1311 697
2 907 1365 1331 1314 698
3 884 1343 1313 1324 692
4 973 1413 1381 1408 729
5 817 1208 1207 1208 641

按我们平常的思路想的话:编号1 每次都访问引用类型的属性,它最有可能是最慢的编号1的确没有快到哪里去,但是最后发现编号1编号2编号3居然差不多一样快,于是本博客主就去搜了一下资料,刚刚好看到 “C#是强类型检查”,就顺便对 强类型检查 知识记笔记, 强类型检查语言对于访问数组的时候,要对索引的有效值进行判断,于是在 for循环 内部遍历数组时,都会转化为下面这样的形式:

/* 这个是原本的形式(编号2)
int boolArrayLength = boolArray.Length;
for(int i = 0; i < boolArrayLength; ++i)
{bool tempBool = boolArray[i];
}
*/int boolArrayLength = boolArray.Length;
for(int i = 0; i < boolArrayLength; ++i)
{if(i < boolArrayLength.Length){bool tempBool = boolArray[i];}else throw new IndexOutOfRangeException();
}

这种强类型检查就好比我们写 属性 的时候,进行一次判断一样,更直观的就是直接举 索引器 的例子:

//属性使用时的判断
private Monster monster;
public Monster OwnedMonster
{get{if(monster != null)return monsterelsethrow new UnassignedReferenceException();}
}//索引器使用时的判断
private string[] faceStrs;
public string this[int faceIndex]
{get{if(faceIndex < faceStrs.length)return faceStrs;elsethrow new IndexOutOfRangeException();}
}

对于编号4中的方法:

public static IntPtr UnsafeAddrOfPinnedArrayElement(Array arr, int index);

这个静态函数的作用是返回一个数组第 index 个元素的首地址,而且没有值类型和引用类型的限制,没有数组索引越界的检查
简单的说,它是一个 托管的C#数组非托管指针 的一个合法的转换接口
在改变数组值的时候没有进行索引越界检查,效率会略微提高。

<资料来源于 C# 内存操作常用函数.>

而对于 编号0 时间消耗的意外凹陷,本博客主想挺久的,最后再决定在控制台编译:

static int count = 100_000;static void Main(string[] args)
{bool[] boolArray = new bool[count];for (int i = 0; i < count; ++i)boolArray[i] = true;loop:for (int testIndex = -1; testIndex < 4; ++testIndex){Stopwatch timer = new Stopwatch();timer.Start();switch (testIndex){//传统"预热"case -1:int u = 0;break;//编号0: 正常循环 case 0:for (int i = 0; i < count; ++i) {boolArray[i] = false; }break;//编号1: 每次循环都会 .Length case 1:for (int i = 0; i < boolArray.Length; ++i) {boolArray[i] = false; }break;//编号2: 储存了 boolArrayLength case 2:int boolArrayLength = boolArray.Length;for (int i = 0; i < boolArrayLength; ++i) {boolArray[i] = false;}break;//编号3: 倒着循环 case 3:for (int i = boolArray.Length - 1; i >= 0; --i){boolArray[i] = false;}break;//编号4: 指针循环case 4:unsafe{bool* pBoolArray = (bool*)System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(boolArray, 0);for (int i = 0; i < boolArray.Length; ++i){ pBoolArray[i] = false;}}break;}timer.Stop();Console.WriteLine($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");}Console.ReadLine();goto loop;
}

运行,并在编译完成后记录实验数据

循环次数/循环编号 编号0 编号1 编号2 编号3 编号4
1 2058 1823 2058 1500 1909
2 2050 1572 2117 1502 1913
3 2046 2297 2052 1500 1942
4 2061 1568 2060 1555 1956
5 2068 1566 2067 1503 1925

也印证了上面所说的。
编号0编号2要比编号1强类型检查 ,而编号4用指针循环数组因为不用进行 强类型检查 时间消耗稍微比编号0编号2好,而编号3因为是倒着循环,应该也是少了一次对 boolArray.Length > 0 进行一次检查了。

2. while 循环测试

while循环 标准格式:

while(条件){ 代码块操作 }//条件满足则进入循环主体

流程图展示:

Created with Raphaël 2.3.0进入满足条件?代码块操作离开循环yesno

传说中 while 循环 是很多新手在战胜死循环前要跨过的最高的山。

(1). 与 for 循环的比较

与上面几次测试一样,用简单的自增循环进行测试,测试看看两个循环在进行相同操作时消耗的时间是否会相同:

...
public int count = 100_000;
...private void OnClicked()
{for (int testIndex = -1; testIndex < 4; ++testIndex){Stopwatch timer = new Stopwatch();timer.Start();switch (testIndex){//"预热"升级版case -1:int u = 0;break;//简单的 while不死循环case 0:int i_while = 0;while (i_while < count)++i_while;break;//简单的 for循环case 1:for (int i_for = 0; i_for < count; ++i_for) { }break;//for循环 变体1号case 2:int i_forVariant_1 = 0;for (; i_forVariant_1++ < count;) { }break;//for循环 变体2号case 3:int i_forVariant_2 = 0;for (; i_forVariant_2 < count; ++i_forVariant_2) { }break;}timer.Stop();UnityEngine.Debug.Log($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");}
}

在 unity 上编译完成后记录实验数据

次数 while 循环 for 循环 for 循环变体1 for 循环变体2
1 376 372 373 373
2 336 338 337 336
3 329 326 325 324
4 315 314 316 315
5 258 258 258 289

因为在 Unity 上编译 的实验结果没有太大区别,本博客主决定再在控制台再进行一次记录:

将这行代码
UnityEngine.Debug.Log($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");
改为
Console.WriteLine.Log($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");

控制台编译完成后记录实验数据

次数 while 循环 for 循环 for 循环变体1 for 循环变体2
1 1269 1254 1605 1265
2 1300 1268 1591 1273
3 1254 1249 1602 1258
4 1276 1238 1590 1235
5 1391 1259 1591 1238

显而易见,for循环while循环 经过一样流程的时间消耗上没有太大区别的,而本博客加上两个 for循环变体是为了添加一些实验变量,丰富实验,增加实验的多样性和趣味性。
具体的有说服力的还是要看汇编的实验结果:

while 与 for 循环的执行效率对比

在汇编实验结果中, while 循环 和 for 循环 执行效率一样的写法如下:

//while 传统写法
while(count-- > 0){ }//for 改进写法
for(; count-- > 0; ){ }

选择 while循环 还是 for循环 还是要视具体情况而定,

  • white 循环适合未知循环次数下进行操作;
  • for 循环适合在已知循环次数下进行操作;

一般来说循环框架本身执行效率对程序的影响不大,重点优化的还是循环体里面的代码。

3. do…while 循环概括

do…while循环 标准格式:

do{ 代码块操作 }while(条件); //条件满足则进入循环主体

流程图展示:

Created with Raphaël 2.3.0进入代码块操作满足条件?代码块操作离开循环yesno

在深入理解计算机一书的第三章3.6.5节中的讲述可以看出在对循环语句进行汇编时,会先将 for, while转换为 do…while

对于 do...while 循环,因为 do...while 循环可以看做是 for循环while循环 的组成部分,可以有之前的实验记录间接得到在经过一样流程do..while循环 的运行效率与其他两个不相上下。
如果大家好奇的话就用学到的关于计时器的知识自主测试吧~

二、函数测试

函数基本上可以分为:

  • 1.按返回值分类:

    • 无返回值
    • 单返回值
    • 多返回值(out )
  • 2.按传入参数分类:
    • 无参函数
    • 有参函数
      • 值参数
      • 引用参数(ref, out, in)
  • 3.其他分类:
    • 泛型方法
    • 非泛型方法

1.申明,赋值,初始化

申明一个变量:

int a;

赋值给它一个数:

a = 0;

和初始化:

int a = 0;

它们消耗的时间一样吗?
我们在控制台简单进行一个测试:

...
public static int count = 100_000;public static float testNum = 0;
public const float pi = 3.14159265f;
...static void Main(string[] args)
{for (int testIndex = -2; testIndex < 4; ++testIndex){Stopwatch timer = new Stopwatch();timer.Start();switch (testIndex){//"预热"case -2:break;//空白对照case -1:for (int i = 0; i < count; ++i) { }break;//申明变量case 0:for (int i = 0; i < count; ++i) { float testDeclare; }break;//赋值 case 1:for (int i = 0; i < count; ++i) { testNum = pi; }break;//初始化   case 2:for (int i = 0; i < count; ++i) { float testInit = pi; }break;//申明变量 + 赋值 case 3:for (int i = 0; i < count; ++i) { float testDeclare; testDeclare = pi;}break;}timer.Stop();Console.WriteLine($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");}
}

为了放大实验效果,本博客主把循环内部代码块复制六次(即进行六次操作)

编译完成后记录实验数据

次数 空白对照 申明变量 赋值变量 初始化 申明+赋值
1 1299 1272 2065 2066 2075
2 1258 1290 2061 2062 2137
3 1278 1277 2146 2084 2256
4 1291 1270 2066 2060 2077
5 1296 1272 2070 2078 2068

由实验记录可得:

  • 申明变量不产生时间消耗

1.无返回值无参函数测试

无参无返回值函数的标准格式:

访问权限修饰符 void 函数名() { }
//访问权限修饰符:public, private, protected 等等

直接进入正题,用简单的 for循环 测试函数调用的时间消耗:

...
public int count = 100_000;
...private void OnClicked()
{for (int testIndex = -1; testIndex < 6; ++testIndex){Stopwatch timer = new Stopwatch();timer.Start();switch (Mathf.FloorToInt(testIndex*0.334f)){//"预热"case -1:break;//正常循环 case 0:for (int i = 0; i < count; ++i) { }break;//循环内部加入空函数case 1:for (int i = 0; i < count; ++i) {DoNothing();}break;}timer.Stop();UnityEngine.Debug.Log($"{Mathf.FloorToInt(testIndex*0.334f)}: Takes -{timer.Elapsed.Ticks}- ticks");}
}
//空函数
public void DoNothing(){ }

每个运行3遍,编译完成后记录实验数据,并计算稳定数据的平均值:

次数 正常循环 - - 稳定值 空函数循环 - - 稳定值
1 446 439 439 439 440 439 462 440
2 440 440 440 440 439 439 438 439
3 416 405 406 406 405 404 404 404
4 405 405 404 405 404 404 423 404
5 377 361 361 361 361 361 361 361

发现调用空函数对时间消耗没有太大的影响,或许是 Unity 编译器优化这个部分了。
于是本博客主直接在控制台运行代码,并修改了一些代码:

//正常循环 加一个正常初始化
case 0:for (int i = 0; i < count; ++i) {int a = int.MaxValue;}break;//循环内部加入非空函数
case 1:for (int i = 0; i < count; ++i) {DoSomething();}break;//UnityEngine.Debug.Log($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");
//改为
Console.WirteLine($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");//带有看起来没有用的初始化的函数
public void DoSomething()
{int a = int.MaxValue;
}//再与 while循环和 Console.ReadLine() 配合
Console.ReadLine();

再次把每个运行3遍,编译完成后记录实验数据,并计算稳定数据的平均值:

次数 正常循环 - - 稳定值 函数循环 - - 稳定值
1 1342 1283 1317 1314 2207 2222 2207 2212
2 1294 1397 1290 1357 2207 2260 2203 2223
3 1318 1284 1460 1354 2213 2207 2224 2215
4 1301 1289 1367 1319 2201 2203 2202 2202
5 1352 1290 1281 1308 2202 2201 2289 2201

于是这次实验稳稳地证明了 Unity 编译器会对没有用的函数优化掉,而且函数调用会多消耗部分时间。
后来本博客主为了实现真正意义上的实现函数调用的消耗时间测试,在函数里面加了有意义的代码片段:

public float a = 0;//正常循环 加一行“有用”代码
case 0:for (int i = 0; i < count; ++i) {a += 0.003_906_25f; }break;//循环内部加入非空有用函数
case 1:for (int i = 0; i < count; ++i) {AddFloat();}break;//计时结束后
a = 0;//现在这个函数非常有用
public void AddFloat()
{a += 0.003_906_25f;
}

最后一次把每个运行3遍,编译完成后记录实验数据,并计算稳定数据的平均值:

次数 正常循环 - - 平均值 函数循环 - - 平均值
1 2324 2323 2323 2323 2552 2533 2502 2529
2 2357 2348 2330 2345 2554 2395 2490 2478
3 2321 2315 2323 2320 2396 2441 2502 2446
4 2339 2325 2322 2329 2505 2494 2539 2513
5 2328 2343 2323 2331 2597 2521 2472 2530

由最终结果可知,函数调用对执行时间有略微的影响,从数据上可以看出大概是多出 5%~8%个循环框架 的时间消耗.
而且由于调用函数的时候,系统会在都会在栈上开辟部分内存,具体的函数内存分配见<深入理解函数调用堆栈>。

2.传参和有返回值函数测试

正常函数的标准格式:

访问权限修饰符 返回值类型 函数名(/*传参修饰符*/ 参数类型 参数名,....)
{代码块操作;return 返回值; //如果返回值类型为 void 则可以不写 return ;
}

如果函数比较短,还可以直接用 Lambda表达式

//Lambda 表达式写法
访问权限修饰符 返回值类型 函数名(参数类型 参数名,....) => 返回值;

下面这些函数当做更直观地感受不同的函数:

//无参有返回值
public bool Die()
{if (this.blood <= 0) return true;else return false;
}
//有参有返回值
private int Pay(int given)
{return given - cost;
}
//有参无返回值
protected void AddBlood(int bloodAdd)
{this.blood += bloodAdd;if (this.blood > maxBlood) this.blood = maxBlood;
}
//传参函数
public void Swap(ref int a, ref int b)
{a ^= b;b ^= a;a ^= b;
}
//多返回值函数
public void Adder(bool input_1, bool input_2, bool input_3, out bool sum, out bool carry)
{bool arrear_1 = input_1 ^ input_2;bool arrear_2 =  arrear_1 ^ input_3;bool head_1 = input_1 & input_2;bool head_2 = head_1 & input_3;sum = arrear_2;carry = head_1 | head_2;
}
//Lambda 表达式写法
public float GetArea(int width, int length)=> width * Length;

进入测试环节,我们在循环的自增部分改为函数表示:

...
public static int count = 100_000;
...static void Main(string[] args)
{for (int testIndex = -1; testIndex < 12; ++testIndex){Stopwatch timer = new Stopwatch();timer.Start();switch (Mathf.FloorToInt(testIndex*0.334f)){//传统"预热"case -1:int u = 0;break;//编号0:正常循环 case 0:for (int i = 0; i < count; ++i) { }break;//编号1:有参有返回值函数循环case 1:for (int i = 0; i < count; i = Increase(i)) { }break;//编号2:有参无返回值函数循环case 2:for (int i = 0; i < count; Increase(ref i)) { }break;//编号3:无意义的有参有返回值函数循环case 3:for (int i = 0; i < count; i = AddOne(in i)) { }break;}timer.Stop();Console.WriteLine($"{Mathf.FloorToInt(testIndex*0.334f)}: Takes -{timer.Elapsed.Ticks}- ticks");}
}
//有参有返回值函数
private int Increase(int num) => ++num;
//有参无返回值函数
private void Increase(ref int num) { ++num; }
//无意义的传参有返回值
//因为 in 是只读传参,所以无法 ++num
//in 适用于传入带有大型数据结构的 struct;
private int AddOne(in int num) => num + 1;

同样每个运行3遍,编译完成后记录实验数据,并计算稳定数据的稳定值:

次数 编号0 - - 稳定值 编号1 - - 稳定值 编号2 - - 稳定值 编号3 - - 稳定值
1 1768 1769 1724 1753 6190 5075 5298 5186 3591 2385 2374 2380 3611 3226 3176 3201
2 1245 1257 1250 1251 5070 5039 5046 5052 2374 2373 2381 2376 3186 3184 3190 3187
3 1258 1267 1251 1259 5073 5032 5045 5050 2373 2372 2372 2372 3180 3178 3184 3181
4 1268 1231 1249 1249 5084 5089 5089 5087 2377 2374 2382 2378 3176 3176 3202 3184
5 1296 1230 1259 1261 5110 5056 5082 5083 2387 2381 2391 2386 3177 3178 3189 3181

四种循环平均用时比大概是 2:8:4:5 ,本博客主就用不严谨的但能够生动形象地理解的比喻:

  • i < count 条件判断占 1
  • ++i 或 +1 占 1
  • i = ? 赋值占 1
  • 引用函数占 1
  • 函数返回值占 1
  • 那么最后 实参的值复制给形参占 3

这种解释大家也仅供娱乐就好,不要真的这样做

对于函数对时间性能的影响也没有比其拓展性和易用性重要,不要因为追求”跑得快“而把代码写得一坨一坨乱七八糟的,大家还是要看情况而选择要不要调用函数。
一般来说,对于冗余的重复性高的代码块,就可以用函数套起来,更多是为了以后更新迭代的方便。
把函数用妙了,程序整体性能就能得到有效的提高。

3.事件函数测试

重新复习一下委托怎么写:

访问权限修饰符 delegate 返回值类型 委托名(/*传参修饰符*/ 参数类型 参数名,....);

有了委托,我们可以把函数当参数来使用,关于委托的这里不多说,了解和学习委托链接:

Unity系列之C#四部曲-C#进阶(唐老狮).

本博客主是想用委托引出事件和匿名函数:

//用到的命名空间
using System;//无参无返回值的匿名函数
Action 匿名函数名 = delegate () { };
//有参无返回值的匿名函数
Action<参数类型 参数名称,....> 匿名函数名 = delegate (参数类型 参数名称,....) { };
//无参有返回值的匿名函数
Func<返回值类型 返回值名称> 匿名函数名 = delegate () { return 返回值; };
//有参有返回值的匿名函数
Func<参数类型 参数名称,....,返回值类型 返回值名称> 匿名函数名 = delegate (参数类型 参数名称,....){ return 返回值; };
//匿名函数 Lambda表达式 的其中一个例子
Action<参数类型 参数名称,....> 匿名函数名 = (参数类型 参数名称,....) => { };

接下来进行简单匿名函数执行所需时间:

...
public static int count = 100_000;public delegate int IncreaseDelegate(int num);
...static void Main(string[] args)
{for (int testIndex = -1; testIndex < 4; ++testIndex){Stopwatch timer = new Stopwatch();timer.Start();switch (testIndex){//传统"预热"case -1:int u = 0;break;//编号0:正常循环 case 0:for (int i = 0; i < count; ++i) { }break;//编号1:有参有返回值函数循环case 1:for (int i = 0; i < count; i = Increase(i)) { }break;//编号2:事件匿名函数循环case 2:Func<int, int> increase = (int num) => ++num;for (int i = 0; i < count; i = increase(i)) { }break;//编号3:委托匿名函数循环case 3:IncreaseDelegate increaseDelegate = (int num) => ++num;for (int i = 0; i < count; i = increaseDelegate(i)) { }break;}timer.Stop();Console.WriteLine($"{testIndex}: Takes -{timer.Elapsed.Ticks}- ticks");}
}//有参有返回值函数
private int Increase(int num) => ++num;

编译完成后记录实验数据

次数 编号0 编号1 编号2 编号3
1 127 506 539 537
2 127 511 558 538
3 181 520 568 562
4 129 510 540 543
5 129 507 538 538

可以发现使用委托或事件比用普通函数消耗性能,区别不大。
不过在一个类中没有函数方法的传递的话就尽量不要用委托或事件。

新人博主,请大家多多光照~~如果有什么不正确或不足的地方请在评论区积极指出哟,一起学习一起进步~

测试Unity中常用代码的运行所需时间:循环和函数篇 #性能测试 # for,foreach,while循环 #委托事件相关推荐

  1. 测试Unity中常用代码的运行所用时间:三维向量和变换位移篇 #性能测试 #Vector3 #tranform.position

    C# Vector3 和 transform.position 篇 〇.前言 系统自带计时器 -System.Diagnostics.StopWatch- 的使用 一.三维向量测试 1. 三维向量的函 ...

  2. SiKi学院 Unity中常用api学习笔记(001-014)

    Api 应用程序编程接口 前言 笔记是看siki学院中<Unity中常用api>的学习笔记 课程地址:  http://www.sikiedu.com/my/course/59 强烈推荐大 ...

  3. Unity中使用代码将预制加载到场景

    Unity中使用代码将预制加载到场景 大家知道, 在日常修改预制的时候很方便, 我们将预制从资源文件夹往场景上"一拖", 然后就可以进行修改, 然后应用保存即可. 但是如果某些需求 ...

  4. 【Unity3D】资源文件 ① ( Unity 中常用的文件类型 | Unity 文件操作 | 文件系统中查看文件 | 添加文件 | 删除文件 | 导入文件 | 复制文件 | 缩略图显示 )

    文章目录 一.Unity 中常用的文件类型 二.Unity 文件操作 1.文件系统中查看文件 2.添加目录 / 文件 3.删除目录 / 文件 4.导入资源 5.复制资源 6.缩略图显示 7.meta ...

  5. SiKi学院 Unity中常用api学习笔记(015-019)

    Api 应用程序编程接口 前言 笔记是看siki学院中<Unity中常用api>的学习笔记 课程地址:  http://www.sikiedu.com/my/course/59 强烈推荐大 ...

  6. 计算机常用代码,[计算机软件及应用]jaa中常用代码.doc

    [计算机软件及应用]jaa中常用代码 java访问xml文件 Java code import java.io.*; import javax.xml.parsers.DocumentBuilder; ...

  7. 测试工作中常用在线小工具-初级篇

    背景 测试过程中经常需要用到一些工具来校验数据的正确性,并且可以帮助测试人员更好的定位问题,所以我总结了我这个初级测试小白日常测试使用的小工具~ 1.在线json转换: http://www.bejs ...

  8. Unity中C#代码学习用wasd和上下左右键控制物体前后左右上下移动和绕轴旋转

    Unity中C#代码学习用wasd和上下左右键控制物体前后左右上下移动和绕轴旋转 using System.Collections; using System.Collections.Generic; ...

  9. WordPress开发中常用代码(必备)

    很多人在WordPress开发中常用代码,WordPress 相比其它网站程序,最突出的优势:主题模板多,插件多,相关技术文章多,只要你想到的功能,都可以通过插件或者代码实现.现在分享下WordPre ...

最新文章

  1. grep之字符串搜索算法Boyer-Moore由浅入深(比KMP快3-5倍)
  2. windows错误:Failed to import pydot. You must install pydot and graphviz for `pydotprint` to work.
  3. 右左法则----复杂指针解析
  4. 连续特征离散化方法介绍
  5. [单刷 APUE 系列] 第十四章——高级 I/O
  6. bilibili 解析_用 Python 抓取 bilibili 弹幕并分析!
  7. 说说 Android 的 Material Design 设计(四)——卡片式布局
  8. MD(d)、MT(d)编译选项的区别
  9. python hbase_python 操作 hbase
  10. 手动编译安装lanmp centos6.5 64位
  11. C语言I博客作业02
  12. Python 繁体转简体
  13. 苹果超薄触摸显示技术专利曝光:重新定义轻薄
  14. 终于,“亚麻百货”也要来了!
  15. dirname $0
  16. 转载CS231n课程学习笔记
  17. boa linux arm修改网卡,Boa服务器在ARM+Linux上的移植
  18. 百度工程师首次现场演示:“文心千帆”如何可视化微调大模型
  19. 服务端svn配置及首次本地项目上传svn
  20. 如何做好独立的B2C商城系统运营,带来转化与成交?

热门文章

  1. 化工企业双重预防体系数字化综合管理系统
  2. OS - freeRTOS vs Linux
  3. 【写着玩】二维码检测及定位
  4. 智慧楼宇篇 6 —— 室内定位技术(五) - 室内定位技术总结
  5. 北京最牛的医院 最牛的科室排名出炉
  6. Python制作属于自己的有声小说
  7. u盘文件不见但还占用容量文件办法?
  8. 小米手机全球已舍弃“MI”品牌,全面改用“xiaomi”全称品牌
  9. Java学习笔记类对象多态继承(下)
  10. erlang游戏服务器