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

〇、前言

系统自带计时器 -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");

一、三维向量测试

1. 三维向量的函数方法和属性

首先说说 Vector3 的所有函数方法和属性,如下:

  • 重要属性:
----属性名称----
--------------------------属性作用------------------------
Vector3.right返回 new Vector3(1,0,0); ( static )
Vector3.left返回 new Vector3(-1,0,0); ( static )
Vector3.up返回 new Vector3(0,1,0); ( static )
Vector3.down返回 new Vector3(0,-1,0); ( static )
Vector3.forward返回 new Vector3(0,0,1); ( static )
Vector3.back返回 new Vector3(0,0,-1); ( static )
Vector3.one返回 new Vector3(1,1,1); ( static )
Vector3.zero返回 new Vector3(0,0,0); ( static )
.normalized返回该向量的单位向量:
  e ⃗ = n ⃗ ∣ n ⃗ ∣ \ \vec{e} = \frac {\vec{n}} {\vert \vec{n}\vert}  e =n n
.magnitude返回该向量的模:
m a g → = ∣ n ⃗ ∣ \overrightarrow{mag}= \vert \vec{n}\vert mag =n
.sqrMagnitude返回该向量的平方模:
  s q r M a g = n ⃗ ⋅ n ⃗ = ∣ n ⃗ ∣ 2 \ sqrMag = \def\nor{\vec{n}} \nor \cdot \nor = \vert \nor\vert^2  sqrMag=n n =n 2;
  • 常用静态方法:
----属性名称----
返回值类型
-------------------------返回值-----------------------
Angle(Vector3 a, Vector3 b)float返回两个向量的夹角(度数):
  < a ⃗ , b ⃗ > = arccos ⁡ ( a ⃗ ⋅ b ⃗ ∣ a ⃗ ∣ ⋅ ∣ b ⃗ ∣ ) \ \def\fromVec{\mathsf {\vec a}} \def\toVec{\mathsf {\vec b}} <\vec a,\vec b>= \arccos\left( \frac {\fromVec \cdot \toVec} {\vert\fromVec\vert \cdot \vert \toVec\vert} \right)  <a ,b >=arccos(a b a b )
ClampMagnitude(Vector3 a, float maxLength)Vector3返回方向与 a 相同,长度最大为 maxLength 的向量:
v e c → = min ⁡ { a ⃗ . m a g n i t u d e , m a x L e n g t h } ⋅ a ⃗ . n o r m a l i z e \overrightarrow{vec}= \min\{\vec \mathsf a.magnitude,\mathsf {maxLength}\}\cdot \vec \mathsf a.normalize vec =min{a .magnitude,maxLength}a .normalize
Cross(Vector3 a, Vector3 b)Vector3返回两个向量的叉积:
  v e c → = a ⃗ × b ⃗ = ∣ i ⃗ j ⃗ k ⃗ a ⃗ . x a ⃗ . y a ⃗ . z b ⃗ . x b ⃗ . y b ⃗ . z ∣ \ \def\A{\vec\mathsf a} \def\B{\vec\mathsf b} \overrightarrow{vec} = \A \times \B = \begin{vmatrix} \vec i & \vec j & \vec k \\ \A .x & \A.y & \A.z \\ \B.x & \B.y & \B.z\end{vmatrix}  vec =a ×b =i a .xb .xj a .yb .yk a .zb .z
Dot(Vector3 a, Vector3 b)float返回两个向量的点积:
  r e s u l t = a ⃗ ⋅ b ⃗ = a ⃗ . x ∗ b ⃗ . x + a ⃗ . y ∗ b ⃗ . y + a ⃗ . z ∗ b ⃗ . z \ \def\A{\vec\mathsf a} \def\B{\vec\mathsf b} result= \A \cdot \B = \A.x*\B.x+\A.y*\B.y+\A.z*\B.z  result=a b =a .xb .x+a .yb .y+a .zb .z
Scale(Vector3 a, Vector3 b)Vector3返回两个向量各部分对应相乘的结果:
v e c → = n e w   V e c t o r 3 ( a ⃗ . x ∗ b ⃗ . x , a ⃗ . y ∗ b ⃗ . y , a ⃗ . z ∗ b ⃗ . z ) ; \def\A{\vec\mathsf a}\def\B{\vec\mathsf b} \overrightarrow\mathsf {vec} = \mathsf {new \ Vector3}( \A.x*\B.x, \A.y*\B.y, \A.z*\B.z); vec =new Vector3(a .xb .x,a .yb .y,a .zb .z);
Distance(Vector3 a, Vector3 b)float返回ab的距离:
  d i s = ∣ b ⃗ − a ⃗ ∣ \ dis = \vert \vec\mathsf b - \vec\mathsf a\vert  dis=b a
Lerp(Vector3 a, Vector3 b, float t)Vector3返回ab的插值( 其中t 被限制在0~1之间 ):
v e c → = a ⃗ + ( b ⃗ − a ⃗ ) ∗ t \overrightarrow{vec}= \vec\mathsf a + (\vec\mathsf b - \vec\mathsf a)*\mathsf t vec =a +(b a )t
LerpUnclamp(Vector3 a, Vector3 b, float t)Vector3返回ab的插值( t 不会被限制在0~1之间 ):
v e c → = a ⃗ + ( b ⃗ − a ⃗ ) ∗ t \overrightarrow{vec}= \vec\mathsf a + (\vec\mathsf b - \vec\mathsf a)*\mathsf t vec =a +(b a )t
Magnitude(Vector3 vector)float返回 vector 的模:
  m a g = ∣ v e c t o r → ∣ \ mag = \vert\overrightarrow\mathsf {vector} \vert  mag=vector
Normalize(Vector3 vector)Vector3返回 vector 的单位向量:
  e ⃗ = v e c t o r → ∣ v e c t o r → ∣ \ \vec e = \frac {\overrightarrow\mathsf {vector}} {\vert\overrightarrow\mathsf {vector} \vert}  e =vector vector
SqrMagnitude(Vector3 vector)float返回 vector 的平方模:
s q r M a g = v e c t o r → ⋅ v e c t o r → \def\Vec{\overrightarrow\mathsf {vector}} sqrMag = \Vec \cdot \Vec sqrMag=vector vector ;
Max(Vector3 a, Vector3 b)Vector3返回由两个向量最大值部分组成的向量:
  v e c → = n e w   V e c t o r 3 ( max ⁡ { a ⃗ . x , b ⃗ . x } , max ⁡ { a ⃗ . y , b ⃗ . y } , max ⁡ { a ⃗ . z , b ⃗ . z } ) ; \ \def\A{\vec\mathsf a} \def\B{\vec\mathsf b} \overrightarrow{vec}= \mathsf {new \ Vector3}(\max\{\A.x,\B.x\},\max\{\A.y,\B.y\},\max\{\A.z,\B.z\});  vec =new Vector3(max{a .x,b .x},max{a .y,b .y},max{a .z,b .z});
Min(Vector3 a, Vector3 b)Vector3返回由两个向量最小值部分组成的向量:
  v e c → = n e w   V e c t o r 3 ( min ⁡ { a ⃗ . x , b ⃗ . x } , min ⁡ { a ⃗ . y , b ⃗ . y } , min ⁡ { a ⃗ . z , b ⃗ . z } ) ; \ \def\A{\vec\mathsf a} \def\B{\vec\mathsf b} \overrightarrow {vec}= \mathsf {new \ Vector3}(\min\{\A.x,\B.x\},\min\{\A.y,\B.y\},\min\{\A.z,\B.z\});  vec =new Vector3(min{a .x,b .x},min{a .y,b .y},min{a .z,b .z});
MoveTowards(Vector3 current, Vector3 target, float maxDistanceDelta)Vector3返回 current 沿 “ current 指向 target ” 方向移动 maxDistanceDelta 距离后的向量(令 direction = (target - current).Normalized();):
v e c → = c u r r e n t → + d i r e c t i o n → ∗ m a x D i s t a n c e D e l t a \overrightarrow{vec}= \overrightarrow\mathsf {current}+ \overrightarrow\mathsf {direction}*\mathsf {maxDistanceDelta} vec =current +direction maxDistanceDelta
Project(Vector3 vector, Vector3 onNormal)Vector3返回 vectoronNormal 上的投影(令 normal = onNormal.Normalized();):
v e c → = ( v e c t o r → ⋅ n o r m a l → ) ⋅ n o r m a l → \overrightarrow{vec} = \def\Nor{\overrightarrow\mathsf {normal}} \left(\overrightarrow\mathsf {vector}\cdot\Nor \right)\cdot \Nor vec =(vector normal )normal
ProjectOnPlane(Vector3 vector, Vector3 planeNormal)Vector3返回 vector 在面上的投影(令 normal = planeNormal.Normalized();):
v e c → = v e c t o r → − ( v e c t o r → ⋅ n o r m a l → ) ⋅ n o r m a l → \overrightarrow{vec} = \def\Vec{\overrightarrow\mathsf {vector}} \def\Nor{\overrightarrow\mathsf {normal}} \Vec - \left(\Vec \cdot\Nor\right)\cdot \Nor vec =vector (vector normal )normal
Reflect(Vector3 inDirection, Vector3 inNormal)Vector3返回 inDirectioninNormal 上的反射(令 normal = inNormal.Normalized(); dir = inDirection;):
v e c → = d i r → − 2 ∗ ( d i r → ⋅ n o r m a l → ) ⋅ n o r m a l → \overrightarrow\mathsf {vec} = \def\Dir{\overrightarrow\mathsf{dir}} \def\Nor{\overrightarrow\mathsf{normal}} \Dir - 2 *\left(\Dir \cdot \Nor\right) \cdot \Nor vec =dir 2(dir normal )normal
SignedAngle(Vector3 from, Vector3 to, Vector3 axis)float返回两个向量在法向量为 axis 的面上的投影之间的夹角:
∠ a n g l e = A n g l e ( P r o j e c t O n P l a n e ( f r o m ⃗ , a x i s ⃗ ) , P r o j e c t O n P l a n e ( t o ⃗ , a x i s ⃗ ) ) ; \angle angle = \mathsf{Angle(ProjectOnPlane(\vec {\bold {from}},\vec {\bold {axis}}),ProjectOnPlane(\vec {\bold {to}},\vec {\bold {axis}}))}; angle=Angle(ProjectOnPlane(from ,axis ),ProjectOnPlane(to ,axis ));
  • 常用方法:
----属性名称----
返回值类型
-------------------------返回值-----------------------
.Scale(Vector3 scale)void该向量为与 scale 各部分对应相乘的结果:
v e c → = n e w   V e c t o r 3 ( x ∗ s a c l e → . x , y ∗ s a c l e → . y , z ∗ s a c l e → . z ) ; \def\B{\overrightarrow\mathsf {sacle}} \overrightarrow\mathsf {vec} = \mathsf{new \ Vector3}( x*\B.x, y*\B.y, z*\B.z); vec =new Vector3(xsacle .x,ysacle .y,zsacle .z);
.Set(float newX, float newY, float newZ)void不开新的内存空间的改变该向量值的方法:
v e c → = n e w   V e c t o r 3 ( n e w X , n e w Y , n e w Z ) ; \overrightarrow\mathsf {vec} = \mathsf{new \ Vector3}(\mathsf{newX}, \mathsf{newY}, \mathsf{newZ}); vec =new Vector3(newX,newY,newZ);
.Normalize()void单位化该向量:
e ⃗ = n ⃗ n ⃗ . m a g n i t u d e \vec e = \frac{\vec n}{\vec n.\mathsf magnitude} e =n .magnituden

2. 三维向量测试

介绍完三维向量的基本常用函数方法,接下来将进行一系列测试来说明各方法的最佳使用环境。
接下来所有测试都以以下代码为标准代码块(更详细的见测试代码运行所用时间(一)),并在测试不同内容时进行不同修改:

...
// ==!==(修改区 0) 修改 count 大小
private int count = 100_000;
...

private void OnClicked()
{
    // ==!==(修改区 1) 最初初始化代码块
    ...

    for (int testIndex = -1; testIndex < (n/*测试实验个数(预热和对比区除外)*/ + 1) * 3; ++testIndex)
    {
    	// ==!==(修改区 2) 每次初始化代码块
    	...
    
    	
		int index = Mathf.FloorToInt(testIndex * 0.334f);
		
        Stopwatch timer = new Stopwatch();
        timer.Start();
        switch (index)//每个测试 3 次
        {
            //传统"预热"
            case -1:
                break;

            // case 0 一般都为普通循环(对比区)
            case 0:
                for (int i = 0; i < count; ++i) { }
                break;

            // ==!==(修改区 3) 其他测试代码块
			case 1:
				...
				break;
				
			...
        }

        timer.Stop();
        UnityEngine.Debug.Log($"{index}: Takes -{timer.Elapsed.Ticks}- ticks");

        // ==!==(修改区 4) 结束后进行其余操作的代码块
        ...
    }
}

(1). 初始化、创建和赋值

//(修改区 2)
Vector3 dir = Vector3.zero;

其中 测试实验个数 n = 4 
//(修改区 3)
case 1:
    for (int i = 0; i < count; ++i)
    {
    	Vector3 tmpDir = Vector3.up;
    }
    break;
case 2:
    for (int i = 0; i < count; ++i)
    {
        dir = Vector3.up;
    }
    break;
case 3:
    for (int i = 0; i < count; ++i)
    {
        dir = new Vector3(0, 1, 0);
    }
case 4:
    for (int i = 0; i < count; ++i)
    {
        dir.Set(0, 1, 0);
    }
    break;

在Unity中多次测试后取稳定值,记录实验数据

次数\实验编号01234
1608912182455115477
2609912182354885488
3612913182454685511

(2). Distance、magnitude 和 sqrMagnitude

//(修改区 1)
Vector3 targetPos = Random.onUnitSphere * Random.Range(0f, 42f);
Vector3 startPos = Vector3.zero;

//(修改区 2)
float len = 0;

其中 测试实验个数 n = 5 
//(修改区 3)
case 1:
    for (int i = 0; i < count; ++i)
    {
    	len = Vector3.Distance(startPos,targetPos);
    }
    break;
case 2:
    for (int i = 0; i < count; ++i)
    {
        len = targetPos.magnitude;
    }
    break;
case 3:
    for (int i = 0; i < count; ++i)
    {
        len = Vector3.Magnitude(targetPos);
    }
case 4:
    for (int i = 0; i < count; ++i)
    {
        len = targetPos.sqrMagnitude;
    }
    break;
case 5:
    for (int i = 0; i < count; ++i)
    {
        len = Vector3.SqrMagnitude(targetPos);
    }
    break;

在Unity中多次测试后取稳定值,记录实验数据

次数\实验编号012345
162966122123161176275188677
25556579723677204181360911996
350365150220431576970847656
4752638841201513409117128624
563062636123971627471567714

由上述实验数据可得:

在进行向量之间距离的比较时,最好用 sqrMagnitude
如果需要用到向量的模就还是用 magnitude

关于 Distance 和 magnitude 的区别,本博主认为是两个向量差引起的,所以再次进行实验:

//(修改区 1)
Vector3 targetPos = Random.onUnitSphere * Random.Range(0f, 42f);
Vector3 startPos = Vector3.zero;
Vector3 dir = targetPos - startPos;

//(修改区 2)
float len = 0;

其中 测试实验个数 n = 4 
//(修改区 3)
case 1:
    for (int i = 0; i < count; ++i)
    {
    	len = Vector3.Distance(startPos,targetPos);
    }
    break;
case 2:
    for (int i = 0; i < count; ++i)
    {
        len = (targetPos - startPos).magnitude;
    }
    break;
case 3:
    for (int i = 0; i < count; ++i)
    {
        len = Vector3.Magnitude(targetPos - startPos);
    }
case 4:
    for (int i = 0; i < count; ++i)
    {
        len = dir.magnitude;
    }
    break;

再在Unity中进行多次测试后取稳定值,记录实验数据

次数\实验编号01234
183738458404704624710289
210442966140310443439605
38042868639578446139601

由此可发现:

distancemagnitude 中:
如果要计算两点之间的距离,用 distance 是最好的选择;
如果要计算一个向量的模,用 magnitude 是最好的选择;

(3). 由 normalize 引出的各个数字运算耗费时间

一般情况下,我们都会认为 单位化向量 的性能消耗比较大,以为其中有 1 n ⃗ ⋅ n ⃗ \frac {1} {\sqrt{\vec n \cdot \vec n}} n n 1,既有除的操作,又有根号的操作。
于是下面先来测试一下各个运算符的性能消耗:

//(修改区 1)
float creator = 1.0001f;
float sqrtNum = 114514.114514f;
double outsideNum = 0;

//(修改区 2)
double num = 1314.520f;

其中 测试实验个数 n = 8 
//(修改区 3)
case 1:
    for (int i = 0; i < count; ++i)
    {
    	num += creator;
    }
    break;
case 2:
    for (int i = 0; i < count; ++i)
    {
        num -= creator;
    }
    break;
case 3:
    for (int i = 0; i < count; ++i)
    {
        num *= creator;
    }
case 4:
    for (int i = 0; i < count; ++i)
    {
        num /= creator;
    }
    break;
case 5:
    for (int i = 0; i < count; ++i)
    {
    	num %= creator;
    }
    break;
case 6:
    for (int i = 0; i < count; ++i)
    {
        num = sqrtNum;
    }
    break;
case 7:
    for (int i = 0; i < count; ++i)
    {
        num = Mathf.Sqrt(sqrtNum);
    }
case 8:
    for (int i = 0; i < count; ++i)
    {
        num = System.Math.Sqrt(sqrtNum);
    }
    break;

//(修改区 4)
outsideNum = num;//为了让 num 有被使用到,防止被优化没了

在Unity中进行多次测试后取稳定值,记录实验数据

次数\实验编号012345678
18373827377637618161148683175289222418
28793760376137608492147772877295222517
38383950378937608168148903742298612673

本博主为了方便观察,画出直观图

:05 :10 :15 :20 :25 :30 3760~3873 3759~3903 3873~4258 3760~3958 3760~3778 3782~5465 3758~3829 3760~3848 3759~3773 8160~8166 8158~8697 8151~8341 14766~15025 14774~14949 14856~15055 2744~3577 2868~4044 3369~4163 28363~29649 29500~29701 29555~31073 2409~2420 2511~2526 2520~4093 求余 (double)float Unity Sqrt System Sqrt 数字运算性能测试直观图

但是 System.Math.Sqrt 的超短时间计算确实出乎意料。本博主测试了每次测试 count *= 10,结果发现 System.Math.Sqrt 与 count 成线性相关 还是 1:1 那种。而且每次计算结果和 Unity.Mathf.Sqrt 的计算结果一样。

这次意料之外的测试在以后再进行探究。

接下来测试一下 NormalizeSqrt 的时间消耗:

//(修改区 1)
Vector3 targetPos = Random.onUnitSphere * Random.Range(0f, 42f);
Vector3 norDir;
double outsideNum;

//(修改区 2)
double num = 1314.520f;

其中 测试实验个数 n = 7 
//(修改区 3)
case 1:
    for (int i = 0; i < count; ++i)
    {
    	norDir = dir.normalized;
    }
    break;
case 2:
    for (int i = 0; i < count; ++i)
    {
         dir.Normalize();
         norDir = dir;
    }
    break;
case 3:
    for (int i = 0; i < count; ++i)
    {
        Vector3.Normalize(dir);
        norDir = dir;
    }
case 4:
    for (int i = 0; i < count; ++i)
    {
        outsideNum = Mathf.Sqrt((float)num);
    }
    break;
case 5:
    for (int i = 0; i < count; ++i)
    {
        float sqrMag = dir.sqrMagnitude;
        norDir = dir / Mathf.Sqrt(sqrMag);
    }
    break;
case 6:
    for (int i = 0; i < count; ++i)
    {
        float sqrMag = dir.x * dir.x + dir.y * dir.y + dir.z * dir.z;
        norDir = dir / Mathf.Sqrt(sqrMag);
    }
    break;
case 7:
    for (int i = 0; i < count; ++i)
    {
        float sqrMag = dir.x * dir.x + dir.y * dir.y + dir.z * dir.z;
        norDir = dir / System.Math.Sqrt(sqrMag);
    }
    break;

//(修改区 4)
norDir = Vector3.zero;

在Unity中多次测试后取稳定值,记录实验数据

次数\实验编号01234567
15041107908933910299136709828137029550343
2602103803846209776632458809546910549984
3486104342845939793634093846986912947001

由实验数据可知 Normalize 的性能与 直接 1/Mathf.Sqrt(.sqrMagnitude) 没有太大的区别。而且使用 1/System.Math.Sqrt(.sqrMagnitude)会比正常1/Mathf.Sqrt(.sqrMagnitude)快一些,速度比大概是10:7
还比 Normalize 快一倍多。

二、变换系统测试

1. transform 的结构

transform 中的所有属性如下:
transform中的所有属性
transform 中的所有方法如下:transform中的所有方法

2. transform.position 赋值和获取

//(修改区 1)
Vector3 startPos = transform.position;

//(修改区 2)
Vector3 assignPos;

其中 测试实验个数 n = 4 
//(修改区 3)
//获取 transform.position
case 1:
    for (int i = 0; i < count; ++i)
    {
    	assignPos = transform.position;
    }
    break;
//正常 transform.position 加赋值
case 2:
    for (int i = 0; i < count; ++i)
    {
        transform.position += Vector3.up;
    }
    break;
//正常 transform.position 赋值
case 3:
    for (int i = 0; i < count; ++i)
    {
        transform.position = Vector3.up;
    }
    break;
//用向量赋值和 tranform.position 赋值比较
case 4:
    for (int i = 0; i < count; ++i)
    {
        assignPos = Vector3.up;
    }
    break;

//(修改区 4)
transform.position = startPos;

再在Unity中进行多次测试后取稳定值,记录实验数据

次数\实验名称对比组获取加赋值赋值向量赋值
12881182921924741175651949
23811587372673441588882264
35151715822852831535512276

由实验记录可知,单单获取 transform.position 的时间消耗和赋值 transform.position 差不多,甚至还是给普通向量赋值的 70 倍。
可以得出结论:

如果要使一个物体移动,先计算好所有向量的合向量,再给 transform.position 赋值,也不能频繁获取 transform.position ,如果常用的话就先储存在 Vector3 中。

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值