Search Unity

Resolved UWP Build has poor performance (Intense use of CPU)

Discussion in 'Scripting' started by marck_ozz, Apr 9, 2021.

  1. marck_ozz

    marck_ozz

    Joined:
    Nov 30, 2018
    Posts:
    107
    Hello all.

    I have this unity UWP app in developing and I have notice that in the build for UWP the more you use the app the more the performance decrease even the app get unresponsive at all.

    What the app do is: load just one 3d model at the time and let you import diferent textures (png, jpeg, jpg files) from local files to taste them in the model. All of this works fine.

    The main issue is, after the first import the performance is getting worst and after the second almost unresponsive with a huge use of CPU and the 3th doesn't even finish. That doesn't happen when build for Windows Standalone.

    This is what I have test or check by now:

    1.- Playing in the editor runs very well.
    2.- Builded for Windows Standalone runs very well.
    3.- Originally developed on 2019.4.15 LTS then upgrated to 2020.3.3 same result for standalone and UWP.
    4.- Builded on Master and Release configuration same result.
    5.- Builded on Development Build, same result.
    4.- Reload the scene (in the build) doesn't solve anything.

    I have check it with the Unity Profiler and everything looks good at the beginning, even after the firs import files is ok, the problem is that, when the app is getting unresponsive, the profiler stops working at all.

    Those are the result in the Profiler and moniroting with the Windows Task Manager:

    upload_2021-4-9_17-19-40.png

    upload_2021-4-9_17-20-10.png

    upload_2021-4-9_17-20-42.png
    (is the one highlighted)

    As you can see in the Profiler, there is nothing related with scrips taking too long, even when I search for "FileManager" that is the script that I use to import the files and basically the only UWP realted functions that I use in the app, is doesn't even take in count.

    The other problem that you can see in the Task Manager is the intense use of CPU. That's make me think that the main problem is my code and the only diffrence between the UWP and the Standalone is the way check the file size and the import (or copy) the files.

    In the Standalone I use the "System.IO.File" methods for copy, write, delete etc. and for the UWP I use the "Windows APIs" like "Windows.UI" and "Windows.Storage" for the same purpose.

    Finally this is the code that I use to import the files and folders (It has some "Debug.LogError" here and there to see what happens in the Development Build and the Unily log):
    Code (CSharp):
    1. async void FilePickerAsync()
    2.     {
    3. #if ENABLE_WINMD_SUPPORT
    4.         UnityEngine.WSA.Application.InvokeOnUIThread(async () =>
    5.         {
    6.             writeFileOK = false;
    7.             noFile = false;
    8.  
    9.             if (type == 3)
    10.             {
    11.                 //Token Configuration
    12.                 _tokenFolderSource = new CancellationTokenSource();
    13.                 var token = _tokenFolderSource.Token;
    14.                 _tokenDisposed = false;
    15.  
    16.                 //Folder measurement Configuration
    17.                 folderSize = 0;
    18.                 timeElapsed = 0;
    19.                 System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
    20.  
    21.                 //Folder picker configuration
    22.                 var folderPicker = new Windows.Storage.Pickers.FolderPicker();
    23.                 folderPicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop;
    24.                 folderPicker.FileTypeFilter.Add("*");
    25.  
    26.                 Windows.Storage.StorageFolder folder = await folderPicker.PickSingleFolderAsync();
    27.                 if (folder != null)
    28.                 {
    29.                     Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("PickedFolderToken", folder);
    30.                     Debug.LogError("Picked folder: " + folder.Name);
    31.                
    32.                     timer.Start(); //start timer for task
    33.                     //Cancelation task timeout for folder measurement
    34.                     Task cancelTask = Task.Run(() =>
    35.                     {
    36.                         while (timer.Elapsed.TotalSeconds < 45) //If it takes more than 45second to check the foldet size cancel the task
    37.                         {
    38.                         }
    39.  
    40.                         Debug.LogError("Timeout, is taking too long :(");
    41.  
    42.                         if (modelFault == "error_007")
    43.                             Debug.LogError("Got error 007");
    44.                         else
    45.                         {
    46.                             modelFault = "error_007";
    47.                             Debug.LogError("Assign error 007");
    48.                         }
    49.                         _tokenFolderSource.Cancel();
    50.                     });                
    51.  
    52.                     try
    53.                     {
    54.                         Task GetSize = CheckFolderSize(folder, token);
    55.                         await Task.WhenAny(GetSize, cancelTask);
    56.                         timer.Stop();
    57.                         Debug.LogError("Check size done with: " + folderSize / 1000000 + "MB" + " and " + timer.Elapsed.TotalSeconds + "seg");
    58.  
    59.                         if(modelFault == "")
    60.                         {
    61.                             if (folderSize < 500000000) //only less than 500MB allowed
    62.                             {
    63.                                 if (Directory.Exists(writeFolder))
    64.                                 {
    65.                                     sktchFolderfail = false;
    66.  
    67.                                     string root = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
    68.                                     string path = root + @"\sketch";
    69.  
    70.                                     Windows.Storage.StorageFolder sketchFolder = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(path);
    71.  
    72.                                     Debug.LogError("Folder to copy " + folder.Path);
    73.  
    74.                                     FolderPath_ = Path.Combine(sketchFolder.Path, folder.Name);//Get the full path to the new model folder
    75.  
    76.                                     Task cancelCopyTask = Task.Run(() =>
    77.                                     {
    78.                                         while (picsCount <= 10) //If there are more than 5 images in the selected folder, cancel the task
    79.                                         {
    80.                                         }
    81.  
    82.                                         Debug.LogError("More than 10 pics found :(");
    83.  
    84.                                         if (modelFault == "error_004")
    85.                                             Debug.LogError("Got error 004");
    86.                                         else
    87.                                         {
    88.                                             modelFault = "error_004";
    89.                                             Debug.LogError("Assing error 004");
    90.                                         }
    91.                                         _tokenFolderSource.Cancel();
    92.                                     });
    93.  
    94.                                     try
    95.                                     {
    96.                                         Task GetCopy = CopyFolder(folder, sketchFolder, token);
    97.  
    98.                                         Debug.LogError("Start copy folder");
    99.                                         await Task.WhenAny(GetCopy, cancelCopyTask);
    100.                                         Debug.LogError("Await copy folder done");
    101.                                         switch (picsCount)
    102.                                         {
    103.                                             case 0:
    104.                                                 writeFileOK = false;
    105.                                                 done_ = false;
    106.                                                 modelFault = "error_003"; //no images files in the selected folder (png, jpeg etc)
    107.                                                 Debug.LogError("Case 0");
    108.                                                 break;
    109.                                             case 1:
    110.                                                 writeFileOK = true;
    111.                                                 done_ = true;
    112.                                                 Debug.LogError("Done 01"); //at least 1 pic found
    113.                                                 sktchFolderfail = false;
    114.                                                 Debug.LogError("Case 1");
    115.                                                 picsCount = 0;
    116.                                                 modelFault = "";
    117.                                                 break;
    118.                                             case 2:
    119.                                                 writeFileOK = false;
    120.                                                 done_ = false;
    121.                                                 Debug.LogError("Case 2"); //more than 10 pics found
    122.                                                 break;
    123.                                             default:
    124.                                                 writeFileOK = false;
    125.                                                 done_ = false;
    126.                                                 Debug.LogError("Case default"); //for control
    127.                                                 break;
    128.                                         }
    129.                                     }
    130.                                     catch (Exception e)
    131.                                     {
    132.                                         Debug.LogError("Operation canceled" + e);
    133.                                     }
    134.                                     finally
    135.                                     {
    136.                                         Debug.LogError("Copy Folder task done 01");
    137.                                     }
    138.                                 }
    139.                                 else
    140.                                 {
    141.                                     // Basically the same code but first create the "writeFolder"
    142.                                 }
    143.                             }
    144.                             else
    145.                             {
    146.                                 Debug.LogError("Folder too big.");
    147.                                 path_ = "";
    148.                                 done_ = false;
    149.                                 writeFileOK = false;
    150.                                 noFile = true;
    151.                                 modelFault = "error_005"; //Folder too big, up to 500MB by folder allowed, Sorry!
    152.                             }
    153.                         }
    154.                     }
    155.                     catch(Exception e)
    156.                     {
    157.                         Debug.LogError("End with exception: " + e);
    158.                     }
    159.                     finally
    160.                     {
    161.                         importReq = false;
    162.                         _tokenFolderSource.Dispose();
    163.                     }
    164.                 }
    165.                 else
    166.                 {
    167.                     Debug.LogError("Operation cancelled.");                
    168.                     path_ = "";
    169.                     done_ = false;
    170.                     noFile = true;
    171.                     importReq = false;
    172.                 }
    173.  
    174.                 async Task CheckFolderSize(Windows.Storage.StorageFolder thisFolder, CancellationToken token_)
    175.                 {
    176.                     if (token_.IsCancellationRequested)
    177.                         return;
    178.  
    179.                     if (timer.Elapsed.TotalSeconds > 45)
    180.                     {
    181.                         if(modelFault == "error_007")
    182.                             return;
    183.                         else
    184.                         {
    185.                             modelFault = "error_007";
    186.                             Debug.LogError("Assign error 007");
    187.                             return;
    188.                         }
    189.                     }
    190.  
    191.                     Debug.LogError("Task has started");
    192.  
    193.                     List<Task> tasks = new List<Task>();
    194.  
    195.                     await FileSize(thisFolder, token_);
    196.  
    197.                     Debug.LogError("Get files size done.");
    198.  
    199.                     foreach (var folders in await thisFolder.GetFoldersAsync())
    200.                     {
    201.                         if (timer.Elapsed.TotalSeconds > 45)
    202.                         {
    203.                             if (modelFault == "error_007")
    204.                                 break;
    205.                             else
    206.                             {
    207.                                 modelFault = "error_007";
    208.                                 Debug.LogError("Assing er007");
    209.                                 break;
    210.                             }
    211.                         }
    212.                         else
    213.                         {
    214.                             tasks.Add(CheckFolderSize(folders, token_));
    215.                             Debug.LogError("One folder found at " + folders.Path);
    216.                         }
    217.  
    218.                         if (token_.IsCancellationRequested)
    219.                             return;
    220.                     }
    221.  
    222.                     await Task.WhenAll(tasks);
    223.  
    224.                     timeElapsed += timer.Elapsed.TotalSeconds;
    225.  
    226.                     Debug.LogError("Task has DONE in: " + timer.Elapsed.TotalSeconds + "seg");
    227.                 }
    228.  
    229.                 async Task FileSize(Windows.Storage.StorageFolder inFolder, CancellationToken token_)
    230.                 {
    231.                     Debug.LogError("Time elapsed : " + timer.Elapsed.TotalSeconds + "seg");
    232.                     if (token_.IsCancellationRequested)
    233.                         return;
    234.  
    235.                     if (timer.Elapsed.TotalSeconds > 60)
    236.                     {
    237.                         if (modelFault == "error_007")
    238.                             return;
    239.                         else
    240.                         {
    241.                             modelFault = "error_007";
    242.                             Debug.LogError("Assing er007");
    243.                             return;
    244.                         }
    245.                     }
    246.                     else
    247.                     {
    248.                         var fileSizeTasks = (await inFolder.GetFilesAsync()).Select(async file => (await file.GetBasicPropertiesAsync()).Size); //Get the size of all the files in the actual folder
    249.                    
    250.                         var sizes = await Task.WhenAll(fileSizeTasks);
    251.                         var thisSize = sizes.Sum(l => (long)l);
    252.                         folderSize += thisSize;
    253.  
    254.                         Debug.LogError("One Task Done");
    255.                     }
    256.                 }
    257.  
    258.                 async Task CopyFolder(Windows.Storage.StorageFolder folderToCopy, Windows.Storage.StorageFolder targetFolder, CancellationToken token1)
    259.                 {
    260.                     if (token1.IsCancellationRequested)
    261.                         return;
    262.  
    263.                     if (picsCount > 10)
    264.                     {
    265.                         if (modelFault == "error_004")
    266.                             return;
    267.                         else
    268.                         {
    269.                             modelFault = "error_004";
    270.                             Debug.LogError("Assing er004"); //more than 10 pics in actual folder
    271.                             return;
    272.                         }
    273.                     }
    274.                
    275.                     if (!FolderExist(targetFolder.Path, folderToCopy.Name))
    276.                         await targetFolder.CreateFolderAsync(folderToCopy.Name, Windows.Storage.CreationCollisionOption.ReplaceExisting);
    277.  
    278.                     Windows.Storage.StorageFolder destination = await targetFolder.GetFolderAsync(folderToCopy.Name);
    279.  
    280.                     Debug.LogError("folder created: " + destination.Path);
    281.  
    282.                     foreach (var file in await folderToCopy.GetFilesAsync())
    283.                     {
    284.                         if (token1.IsCancellationRequested)
    285.                             return;
    286.  
    287.                         if (picsCount > 10)
    288.                         {
    289.                             if (modelFault == "error_004")
    290.                                 return;
    291.                             else
    292.                             {
    293.                                 modelFault = "error_004";
    294.                                 Debug.LogError("Assing er004"); //more than 10 pics in actual folder
    295.                                 return;
    296.                             }
    297.                         }
    298.  
    299.                         string ext = Path.GetExtension(file.Name);
    300.  
    301.                         HashSet<string> set = new HashSet<string>() { ".png", ".jpg", ".jpeg", ".bmp" };
    302.                         if (set.Contains(ext))
    303.                         {
    304.                             if (ext == ".png", || ext == ".jpg", || ext == ".jpeg", || ext == ".bmp") //just copy images files, else don't copy trash file
    305.                             {
    306.                                 picsCount++;
    307.                                 Debug.LogError("pic count + 1");
    308.  
    309.                                 if (picsCount > 1)
    310.                                 {
    311.                                     modelFault = "error_004";
    312.                                     Debug.LogError("model = error04");
    313.                                     break;
    314.                                 }
    315.                                 else
    316.                                 {
    317.                                     //Just get the name and path of each pic                            
    318.                                     path_ = Path.Combine(destination.Path, file.Name);
    319.                                     string writePath = path_;
    320.  
    321.                                     writePathObj = writePath;
    322.                                     importedName = Path.GetFileNameWithoutExtension(file.Name);
    323.  
    324.                                     Debug.LogError("Pic File: " + writePathObj);
    325.                                 }
    326.                             }
    327.                             await file.CopyAsync(destination, file.Name, Windows.Storage.NameCollisionOption.ReplaceExisting);
    328.                         }
    329.                         else
    330.                         {
    331.                             Debug.LogError("Trash File: " + file.Path);
    332.                         }
    333.                     }
    334.  
    335.                     foreach (var newFolder in await folderToCopy.GetFoldersAsync()) //check for more folders in the directory to copy and make the operation recursive
    336.                     {
    337.                         if (token1.IsCancellationRequested)
    338.                             break;
    339.  
    340.                         if (picsCount > 1)
    341.                         {
    342.                             if (modelFault == "error_004")
    343.                                 break;
    344.                             else
    345.                             {
    346.                                 modelFault = "error_004";
    347.                                 Debug.LogError("Assing er004");
    348.                                 break;
    349.                             }
    350.                         }
    351.                    
    352.                         await CopyFolder(newFolder, destination, token1);
    353.                     }
    354.                 }
    355.  
    356.                 bool FolderExist(string destination, string foldername)
    357.                 {
    358.                     string FolderPath_ = Path.Combine(destination, foldername);//Get the full path to the new model folder
    359.                     if (Directory.Exists(FolderPath_))
    360.                         return true;
    361.                     else
    362.                         return false;
    363.                 }
    364.             }
    365.         }, true);
    366. #endif
    367.         await new WaitForSeconds(0.0f); // added to avoid the CS1998 warning since "await" is under platform dependant code
    368.     }

    As I say, that works just fine, and does what suppose to do. By checking the app log, it does what suppose todo too. Nothing is stocked or looping (that was my first guess). The problem is the performance getting worst every time I call this script.

    My best guess is that that code is very CPU intensive or something in the Build is "doing a lot of extra work" and because UWP is very sandboxed and I can't see that on the Profiler.

    I'll thanks any help, advice or point me to the right direction.
     
    Last edited: Apr 11, 2021
  2. marck_ozz

    marck_ozz

    Joined:
    Nov 30, 2018
    Posts:
    107
    Looking for a tool to get the running info or profile a UWP, I found that they have an API that do the work:
    https://blogs.windows.com/windowsdeveloper/2017/06/28/uwp-app-diagnostics/

    and they have put a sample code here:
    https://github.com/microsoft/AppModelSamples/tree/master/Samples/UWPTaskMonitor

    And fortunately, they put an app in the Windows store here:
    https://www.microsoft.com/en-us/p/UWPTaskMonitor/9PNC4SL3XFHR?activetab=pivot:overviewtab

    So by using that app I get this info:

    upload_2021-4-11_17-52-35.png

    upload_2021-4-11_17-52-43.png

    upload_2021-4-11_17-52-50.png

    As you can see there is not any backgroundtask running that could explain the high CPU use and there is only 1 process runnig at the time. I think that the info in the report doesn't explain the CPU usage and and even less why doesn't decrece once the app stops being used (not Closed).
     

    Attached Files:

    Last edited: Apr 11, 2021
  3. marck_ozz

    marck_ozz

    Joined:
    Nov 30, 2018
    Posts:
    107
    By a suggested answer in this microsoft forum: https://docs.microsoft.com/en-us/answers/questions/352355/uwp-unity-app-bad-performance.html, I rewrote my code and I realized that the problem was that as you can see I start the "cancelTask" and "cancelCopyTask" Tasks but NEVER canceled it after the jobs has done, so they keep running and consuming a lot of CPU time.

    The solutions was to add a cancellation token to those Tasks and cancel them once "CheckFolderSize" and "CopyFolder" has finish like this: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation