異なるUnity3Dスクリプト盞互䜜甚オプションの速床の比范

゚ントリヌ


私はナニティの耇雑さに぀いおはあたり知りたせん。なぜなら、私はプロずしおではなく、趣味ずしおそれをやるからです。 通垞、必芁に応じお必芁なものをすべお勉匷するので、この蚘事は私のような人を察象ずしおいたす。


私は、おそらく結束に぀いお曞き始めた人ず同じように、シングルトンマネヌゞャヌ、Find、GetComponentなどを介した最も平凡な察話方法では䞍十分であり、新しいオプションを探す必芁があるこずにすぐに気付きたした。


そしお、メッセヌゞ/通知システムがシヌンに入りたす


さたざたな蚘事を読み、このシステムを実装するためのいく぀かの異なるオプションを芋぀けたした。



ほずんどの蚘事では、特定のアプロヌチのパフォヌマンス、それらの比范などに関する情報はほずんどありたせん。 通垞、 「極端な堎合にのみSendMessageを䜿甚し、たったく䜿甚しない」ずいう速床に぀いおの蚀及のみがありたす。


さお、このアプロヌチには重倧な速床の問題があるようですが、他の人はどうですか


私は正気を芋぀けるこずができず、この質問に関する情報を泚文しおそらく私はひどく芋おいたした、実隓的にそれを芋぀けるこずを決定し、同時にこれらのアプロヌチを実際に詊しおみるこずにしたした。


これら3぀のアプロヌチず、リンクによるオブゞェクトの通垞の盎接関数呌び出しを比范するこずにしたした。
おたけずしお、オブゞェクトを怜玢するずきにFindがどのように動䜜するかを芋おみたしょう。各Update初心者向けのすべおのガむドが叫んでいたすが運転したした。


スクリプトの準備


テストのために、シヌン䞊に2぀のオブゞェクトを䜜成する必芁がありたす。



レシヌバヌReceiver.csから始めたしょう。 コヌドが少なくなりたす。
実際には、最初は、倖郚から呌び出される空の関数に限定するこずを考えたした。 そしお、このファむルはシンプルに芋えるでしょう


using UnityEngine; public class Receiver : MonoBehaviour { public void TestFunction(string name) { } } 

しかし、埌で、すべおの呌び出し/メッセヌゞの送信の実行時間を、送信者だけでなく受信者でも特定するこずにしたした信頌性のため。


これには、4぀の倉数が必芁です。


  float t_start = 0; //    float t_end = 0; //    float count = 0; //    int testIterations = 10000; //   .   10000  

そしお、 TestFunctions関数を远加しお、 testIterationsを 1回完了するのにかかった時間をカりントし、この情報をコン゜ヌルに出力できるようにしたす。 匕数では、文字列testNameを䜿甚したす。 テスト文字列には、テストするメ゜ッドの名前が入りたす。 関数自䜓は、誰がそれを呌び出すかを知りたせん。 この情報は、コン゜ヌルぞの出力にも远加されたす。 その結果、以䞋が埗られたす。


  public void TestFunction(string testName) { count++; //     //     ,     if (count == 1) { t_start = Time.realtimeSinceStartup; } //    ,    ,           (t_end - t_start) else if (count == testIterations) { t_end = Time.realtimeSinceStartup; Debug.Log(testName + " SELF timer = " + (t_end - t_start)); count = 0; } } 

これは完了です。 この関数は、それ自䜓の呌び出しサむクルの実行時間を蚈算し、呌び出した人の名前ずずもにコン゜ヌルに出力したす。
送信者にサブスクラむブし、呌び出しの数を倉曎するために戻りたすただし、送信者の同じ倉数にバむンドしお、2぀の堎所で倉曎しないようにするか、関数に2番目の匕数を枡したすが、私たちはこれに時間を無駄にしたせん


Receiver.csを完党に
 using UnityEngine; public class Receiver : MonoBehaviour { float t_start = 0; //    float t_end = 0; //    float count = 0; //    int testIterations = 10000; //    public void TestFunction(string testName) { count++; //     //     ,     if (count == 1) { t_start = Time.realtimeSinceStartup; } //    ,    ,           (t_end - t_start) else if (count == testIterations) { t_end = Time.realtimeSinceStartup; Debug.Log(testName + " SELF timer = " + (t_end - t_start)); count = 0; } } } 

準備が完了したした。 テストを曞くこずにしたす。


ダむレクトコヌル機胜


Sender.csに移動し、最初のテストのコヌドを準備したす。 最も䞀般的で最も簡単なオプションは、Startで受信者むンスタンスを芋぀け、そのリンクを保存するこずです。


 using System; using UnityEngine; using UnityEngine.Events; public class Sender : MonoBehaviour { float t_start = 0; //    float t_end = 0; //    int testIterations = 10000; //    Receiver receiver; void Start () { receiver = GameObject.Find("Receiver").GetComponent<Receiver>(); } 

DirectCallTest関数を蚘述したす。これは、他のすべおのテスト関数のワヌクピヌスになりたす。


  float DirectCallTest() { t_start = Time.realtimeSinceStartup; for (int i = 0; i < testIterations; i++) { receiver.TestFunction("DirectCallTest"); } t_end = Time.realtimeSinceStartup; return t_end - t_start; } 

各反埩で、レシヌバヌでTestFunctionを呌び出し、テストの名前を枡したす。


コン゜ヌルに結論を出しおこのテストを開始するのは今のたたなので、Startに次の行を远加したす。


  void Start () { receiver = GameObject.Find("Receiver").GetComponent<Receiver>(); Debug.Log("DirectCallTest time = " + DirectCallTest()); } 

できた 最初のデヌタを起動しお取埗したす。 SELFずいう単語を含む結果は、呌び出す関数によっお䞎えられ、SELFを䜿わない堎合は呌び出す関数によっお䞎えられるこずを思い出したす


そのようなプレヌトにそれらを配眮したす


テスト名詊隓時間
DirectCallTestタむマヌ0.0005178452
DirectCallTest SELFタむマヌ0.0001906157

SELFずいう単語を含む結果は、呌び出す関数によっお䞎えられ、SELFを䜿わない堎合は呌び出す関数によっお䞎えられるこずを思い出したす


そのため、コン゜ヌルのデヌタず興味深い画像が衚瀺されたす-受信機の機胜は送信機よりも玄2.7倍速く動䜜したした。
私はただそれが䜕に関連しおいるか理解しおいたせん。 たぶん、受信機で、時間を蚈算した埌、Debug.Logが远加で呌び出されるか、たたは䜕か他のものが...誰かが知っおいるなら、私に曞いお、私はこれを蚘事に远加したす。


いずれにしおも、これは私たちにずっお特に重芁ではありたせん。 異なる実装を互いに比范したいので、次のテストに進みたす。


SendMessageを介しおメッセヌゞを送信する


誰もが叀くお虐埅しおいる...あなたが䜕ができるか芋おみたしょう。


実際には、盎接呌び出しのようにオブゞェクトぞの参照が必芁な堎合、なぜ必芁なのかよくわかりたせん。どうやら、パブリックメ゜ッドを実行しないこずは明らかではありたせん


SendMessageTest関数を远加したす。


  float SendMessageTest() { t_start = Time.realtimeSinceStartup; for (int i = 0; i < testIterations; i++) { receiver.SendMessage("TestFunction", "SendMessageTest"); } t_end = Time.realtimeSinceStartup; return t_end - t_start; } 

Startの行


  Debug.Log("SendMessageTest time = " + SendMessageTest()); 

そのような結果が埗られたすテヌブルの構造を少し倉曎したした


テスト名送信者テスト時間受信者テスト時間
DirectCallTest0.00051784520.0001906157
SendMessageTest0.0043390990.003759265

うわヌ、違いは䞀桁です 匕き続きテストを䜜成し、最終的に分析を行いたす。これにより、すでにすべおを䜿甚できる人は、分析たでスクロヌルできたす。 そしお、これは、私のように、コンポヌネント間の盞互䜜甚のシステムの実装を自分で勉匷し、遞択する人を察象ずしおいたす。


組み蟌みのUnityEventsを䜿甚したす


Sender.csに UnityEventを䜜成し 、その埌、受信者に眲名したす。


  public static UnityEvent testEvent= new UnityEvent(); 

新しいUnityEventTest関数を䜜成しおいたす。


  float UnityEventTest() { t_start = Time.realtimeSinceStartup; for (int i = 0; i < testIterations; i++) { testEvent.Invoke("UnityEventTest"); } t_end = Time.realtimeSinceStartup; return t_end - t_start; } 

Soooo、むベントが発生したすべおの眲名者にメッセヌゞを送信し、 そこに「UnityEventTest」を送信したいのですが、むベントは匕数を受け入れたせん。
マニュアルを読み、このためにUnityEventクラスのタむプを再定矩する必芁があるこずを理解したす。 これを行うずずもに、この行に倉曎を加えたす。


  public static UnityEvent testEvent= new UnityEvent(); 

次のコヌドが刀明したす。


  [Serializable] public class TestStringEvent : UnityEvent<string> { } public static TestStringEvent testStringEvent = new TestStringEvent(); 

UnityEventTestでtestEventをtestStringEventに眮き換えるこずを忘れないでください。


次に、レシヌバヌReceiver.csのむベントにサブスクラむブしたす。


  void OnEnable() { Sender.testStringEvent.AddListener(TestFunction); } 

OnEnableメ゜ッドでサブスクラむブしお、オブゞェクトがシヌンでアクティブ化されたずき䜜成時を含むむベントにサブスクラむブするようにしたす。
たた、オブゞェクトがステヌゞ䞊で切断削陀を含むされたずきに呌び出されるOnDisableメ゜ッドのむベントのサブスクラむブを解陀する必芁がありたすが、テストにはこれが必芁ないため、コヌドのこの郚分は蚘述したせんでした。


始めたす。 すべおうたくいきたす 次のテストに合栌したす。


C むベント/デリゲヌトのむベント


匕数ずしおメッセヌゞを送信する機胜を持぀むベント/デリゲヌトを実装する必芁があるこずを忘れないでください。
Sender.cs sender で 、むベントずデリゲヌトを䜜成したす。


  public delegate void EventDelegateTesting(string message); public static event EventDelegateTesting BeginEventDelegateTest; 

新しい関数EventDelegateTestを䜜成しおいたす。


  float EventDelegateTest() { t_start = Time.realtimeSinceStartup; for (int i = 0; i < testIterations; i++) { BeginEventDelegateTest("EventDelegateTest"); } t_end = Time.realtimeSinceStartup; return t_end - t_start; } 

次に、レシヌバヌReceiver.csのむベントにサブスクラむブしたす。


  void OnEnable() { Sender.testStringEvent.AddListener(TestFunction); Sender.BeginEventDelegateTest += TestFunction; } 

起動しお確認したす。 玠晎らしい、すべおのテストの準備ができたした。


ボヌナス


興味を匕くために、DirectCallTestメ゜ッドずSendMessageTestメ゜ッドのコピヌを远加したす。各反埩で、アクセスする前にステヌゞ䞊のオブゞェクトを怜玢したす。これにより、新芏ナヌザヌは 、このような゚ラヌを発生させるコストを理解できたす。


  float DirectCallWithGettingComponentTest() { t_start = Time.realtimeSinceStartup; for (int i = 0; i < testIterations; i++) { GameObject.Find("Receiver").GetComponent<Receiver>().TestFunction("DirectCallWithGettingComponentTest"); } t_end = Time.realtimeSinceStartup; return t_end - t_start; } float SendMessageTestWithGettingComponentTest() { t_start = Time.realtimeSinceStartup; for (int i = 0; i < testIterations; i++) { GameObject.Find("Receiver").GetComponent<Receiver>().SendMessage("TestFunction", "SendMessageTestWithGettingComponentTest"); } t_end = Time.realtimeSinceStartup; return t_end - t_start; } 

結果分析


それぞれ10,000回の繰り返しのすべおのテストを実行し、そのような結果を取埗したす送信者送信者のサむクルの実行時間ですぐに゜ヌトしたす。この段階で、受信者のテスト時間がDebug.Logを呌び出したす。呌び出しサむクル自䜓の2倍の時間がかかりたした。


テスト名送信者テスト時間
DirectCallTest0.0001518726
EventDelegateTest0.0001523495
UnityEventTest0.002335191
SendMessageTest0.003899455
DirectCallWithGettingComponentTest0.007876277
SendMessageTestWithGettingComponentTest0.01255739

明確にするために、デヌタを芖芚化したす垂盎にすべおの反埩の実行時間、氎平にテストの名前



テストの粟床を䞊げお、反埩回数を1,000䞇回に増やしたしょう。


テスト名送信者テスト時間
DirectCallTest0.1496105
EventDelegateTest0.1647663
UnityEventTest1.689937
SendMessageTest3.842893
DirectCallWithGettingComponentTest8.068002
SendMessageTestWithGettingComponentTest12.79391

原則ずしお、䜕も倉わっおいたせん。 通垞のむベント/デリゲヌトのメッセヌゞシステムの速床は、UnityEventや、さらにはSendMessageに぀いおは蚀えないダむレクトコヌルずほずんど倉わらないこずが明らかになりたす。


最埌の2列は、ルヌプ/曎新でオブゞェクト怜玢を䜿甚するこずからあなたを氞遠に匕き離すず思いたす。



おわりに


少しの研究ずしお、たたはむベントシステムの小さなガむドずしお、誰かがそれを圹に立぀ず思うこずを願っおいたす。


結果のファむルの完党なコヌド


Sender.cs
 using System; using System.Collections; using UnityEngine; using UnityEngine.Events; public class Sender : MonoBehaviour { [Serializable] public class TestStringEvent : UnityEvent<string> { } public delegate void EventDelegateTesting(string message); public static event EventDelegateTesting BeginEventDelegateTest; float t_start = 0; //    float t_end = 0; //    int testIterations = 10000000; //    public static TestStringEvent testStringEvent = new TestStringEvent(); Receiver receiver; System.Diagnostics.Stopwatch stopWatch; void Start () { receiver = GameObject.Find("Receiver").GetComponent<Receiver>(); stopWatch = new System.Diagnostics.Stopwatch(); StartCoroutine(Delay5sec()); // ,      Debug.Log("UnityEventTest time = " + UnityEventTest()); Debug.Log("DirectCallTest time = " + DirectCallTest()); Debug.Log("DirectCallWithGettingComponentTest time = " + DirectCallWithGettingComponentTest()); Debug.Log("SendMessageTest time = " + SendMessageTest()); Debug.Log("SendMessageTestWithGettingComponentTest time = " + SendMessageTestWithGettingComponentTest()); Debug.Log("EventDelegateTest time = " + EventDelegateTest()); // stopWatch.Elapsed.Seconds(); } IEnumerator Delay5sec() { yield return new WaitForSeconds(5); } float UnityEventTest() { //t_start = Time.realtimeSinceStartup; stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < testIterations; i++) { testStringEvent.Invoke("UnityEventTest"); } //t_end = Time.realtimeSinceStartup; //return t_end - t_start; stopWatch.Stop(); return stopWatch.ElapsedMilliseconds / 1000f; } float DirectCallTest() { //t_start = Time.realtimeSinceStartup; stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < testIterations; i++) { receiver.TestFunction("DirectCallTest"); } //t_end = Time.realtimeSinceStartup; //return t_end - t_start; stopWatch.Stop(); return stopWatch.ElapsedMilliseconds / 1000f; } float DirectCallWithGettingComponentTest() { //t_start = Time.realtimeSinceStartup; stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < testIterations; i++) { GameObject.Find("Receiver").GetComponent<Receiver>().TestFunction("DirectCallWithGettingComponentTest"); } //t_end = Time.realtimeSinceStartup; //return t_end - t_start; stopWatch.Stop(); return stopWatch.ElapsedMilliseconds / 1000f; } float SendMessageTest() { //t_start = Time.realtimeSinceStartup; stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < testIterations; i++) { receiver.SendMessage("TestFunction", "SendMessageTest"); } //t_end = Time.realtimeSinceStartup; //return t_end - t_start; stopWatch.Stop(); return stopWatch.ElapsedMilliseconds / 1000f; } float SendMessageTestWithGettingComponentTest() { //t_start = Time.realtimeSinceStartup; stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < testIterations; i++) { GameObject.Find("Receiver").GetComponent<Receiver>().SendMessage("TestFunction", "SendMessageTestWithGettingComponentTest"); } //t_end = Time.realtimeSinceStartup; //return t_end - t_start; stopWatch.Stop(); return stopWatch.ElapsedMilliseconds / 1000f; } float EventDelegateTest() { //t_start = Time.realtimeSinceStartup; stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < testIterations; i++) { BeginEventDelegateTest("EventDelegateTest"); } //t_end = Time.realtimeSinceStartup; //return t_end - t_start; stopWatch.Stop(); return stopWatch.ElapsedMilliseconds / 1000f; } } 

Receiver.cs
 using UnityEngine; public class Receiver : MonoBehaviour { float t_start = 0; //    float t_end = 0; //    float count = 0; //    int testIterations = 10000000; //    void OnEnable() { Sender.testStringEvent.AddListener(TestFunction); Sender.BeginEventDelegateTest += TestFunction; } public void TestFunction(string testName) { count++; //     //     ,     if (count == 1) { t_start = Time.realtimeSinceStartup; } //    ,    ,           (t_end - t_start) else if (count == testIterations) { t_end = Time.realtimeSinceStartup; //Debug.Log(testName + " SELF timer = " + (t_end - t_start));   , .. ,    -     count = 0; } } } 

--= =曎新= =--


私は蚘事でコメントしたいいく぀かの重芁なポむントを曞きたした


1.゚ディタヌ自䜓でテストするこずは䞍可胜ですコヌドは垞にDEBUGモヌドでコンパむルされたす。スタンドアロンビルドを収集しお枬定する必芁がありたす。

  1. N回の繰り返しのルヌプをねじっお結果を取埗するこずはできたせん。 N回の反埩でM回のサむクルを開始し、平均する必芁がありたす。これにより、プロセッサヌの呚波数の倉曎など、さたざたな副䜜甚が滑らかになりたす。
  2. 時間のカりント方法を倉曎する必芁がありたす-Time.realtimeSinceStartupの粟床は䟡倀がありたせん。


いく぀かは非垞に実質的であるため、これらの明確化のために皆に感謝したす。
ずころで、これはこの蚘事のすべおです-Event、ActionList、Observer、InterfaceObserverのパフォヌマンステスト 。 この情報を読んで怜蚎するこずをお勧めしたす。


テストに぀いおは-1および3今すぐ確認したすが、ポむント2特にテストではスキップしたす。 この堎合、各結果の粟床を远求するのではなく、それらを互いに比范したす。 私は手でテストを数回実行したしたが、結果に重倧な逞脱は芋られたせんでしたので、今のずころこれをスキップしたしょう手が届いたら、この瞬間を実感したす


最初に確認するのは


組み立おられたアプリケヌションでテストする


ここではすべおが簡単です-Windowsでアプリケヌションをコンパむルしたす最初にテストを開始する前に5秒の遅延を远加したす。䞇が䞀に備えお、すべおが正確にロヌドされるようにしたす。


次に、 Cに移動したす\ナヌザヌ\ナヌザヌ名\ AppData \ LocalLow \ CompanyName \ ProductName \ output_log.txt
Windowsをお持ちの堎合たたはドキュメントをご芧ください 、ログを調べおください。


そしお、ここでは興味深い倉化が芋られたす



今回のリリヌスではこのような比范が行われたす



䞊蚘の2぀のテストで亀換された堎所に加えお、他に倉曎はなく、グラフはデバッグ時ず同じように芋えたす。 DirectCallずDelegateEventは、UnityEventやその他のむベントよりも高速です


時間カりントを最適化したす


ポむント2のように、時間蚈算の最適化によっお力の調敎に倉化が生じるこずはほずんどありたせんが、この2぀のアプロヌチが倧きく異なる点を芋぀けるためにこのポむントを行いたす。 远われた。


以䞋のすべおを倉曎したす。


 t_start = Time.realtimeSinceStartup; t_end = Time.realtimeSinceStartup; return t_end - t_start; 

オン


 //  System.Diagnostics.Stopwatch stopWatch; //   Start() stopWatch = new System.Diagnostics.Stopwatch(); //      stopWatch.Reset (); stopWatch.Start (); //    stopWatch.Stop (); //  return stopWatch.ElapsedMilliseconds / 1000f; 

最埌に、最終的なコヌド党䜓をレむアりトしたす


したがっお、取埗したデヌタを調べるず、Time.realtimeSinceStartupずSystem.Diagnostics.Stopwatchの違いはこのケヌスでは重芁ではなく、゚ラヌに起因しおいるず蚀えたす実際、DirectCallWithGettingComponentTestテストでは、デバッグバヌゞョンではほが4回の違いがありたすが、リリヌスでは党䜓の比率が再び戻りたす。これを芁玄衚で確認しおください。 私が蚀ったように-チャヌトのビュヌは倉曎されおいたせん



すべおの倀1000䞇回の反埩の実行に費やされた時間で最終版を䜜成したす。 異なるバヌゞョンのデバッグ/リリヌス、ナニティ/ c時間時間枬定方法の最新テストのデヌタ


テスト名デバッグ、Unity時間デバッグ、C時間リリヌス、統䞀時間リリヌス、C時間
DirectCallTest0.14961050.150,04984260,047
EventDelegateTest0.16476630.1550,04787540,047
UnityEventTest1,6899371,6570,57064750.462
DirectCallWithGettingComponentTest8,0680022,1120.87934110.851
SendMessageTest3.8428933,9381,3642391,375
SendMessageTestWithGettingComponentTest12.793916,7522,2502462,244

芁玄するず、リリヌスバヌゞョンでは時間を枬定する新しい方法で実行速床が倧幅に倉曎されたしたが、テスト実行時間の比率は倉曎されおいたせん。 DirectCallずEventDelegateは䟝然ずしおリヌダヌであり、UnityEventよりも10倍高速です。 ただし、各反埩でオブゞェクトを怜玢するDirectCallは、通垞のSendMessageさえも远い越したした。


たた、この曎新から、リリヌスのバヌゞョンが゚ディタヌよりも3〜10倍速いこずがわかりたした。


PS結果には、合蚈実行時間ではなく、1回の反埩の実行時間が必芁であるずいうコメントもありたした。 私たちの堎合、これが必芁だずは思いたせん。 これらの数倀は互いに反比䟋し、わずかに異なる皮類のチャヌト、特に結論のみが倉曎されたす。


䜿甚された文献



ご枅聎ありがずうございたした



Source: https://habr.com/ru/post/J353780/


All Articles