Created
March 14, 2026 20:04
-
-
Save unitycoder/feee34ad09f8ffa7da73ce274cd1d8aa to your computer and use it in GitHub Desktop.
Test DirectStorage loader in unity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // https://docs.unity3d.com/6000.5/Documentation/ScriptReference/Unity.IO.LowLevel.Unsafe.AsyncReadManager.Read.html | |
| // sample file https://files.fm/u/422tvs26s6 | |
| using System; | |
| using System.Collections; | |
| using System.Diagnostics; | |
| using System.IO; | |
| using System.Text; | |
| using Unity.Collections; | |
| using Unity.Collections.LowLevel.Unsafe; | |
| using Unity.IO.LowLevel.Unsafe; | |
| using Unity.Mathematics; | |
| using UnityEngine; | |
| using Debug = UnityEngine.Debug; | |
| public class BinaryLoadBenchmark : MonoBehaviour | |
| { | |
| [Header("File")] | |
| public string filePath; | |
| public bool useStreamingAssetsPath = true; | |
| [Header("Benchmark")] | |
| public int warmupRuns = 1; | |
| public int measuredRuns = 5; | |
| public bool validateVector3Layout = true; | |
| public bool forceConsumeData = true; | |
| private struct BenchmarkResult | |
| { | |
| public string Name; | |
| public int Runs; | |
| public long BytesRead; | |
| public double TotalMs; | |
| public double AvgMs; | |
| public double AvgMBps; | |
| public ulong Checksum; | |
| } | |
| private IEnumerator Start() | |
| { | |
| yield return new WaitForSeconds(1); | |
| RunBenchmark(); | |
| } | |
| [ContextMenu("Run Benchmark")] | |
| public void RunBenchmark() | |
| { | |
| string path = ResolvePath(); | |
| if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) | |
| { | |
| Debug.LogError("File not found: " + path); | |
| return; | |
| } | |
| long fileSize = new FileInfo(path).Length; | |
| if (fileSize <= 0) | |
| { | |
| Debug.LogError("File is empty: " + path); | |
| return; | |
| } | |
| if (fileSize % 12 != 0) | |
| { | |
| Debug.LogWarning("File size is not divisible by 12. Raw Vector3 layout may be invalid. Size: " + fileSize); | |
| } | |
| if (validateVector3Layout) | |
| { | |
| int float3Size = UnsafeUtility.SizeOf<float3>(); | |
| if (float3Size != 12) | |
| { | |
| Debug.LogWarning("float3 size is " + float3Size + ", expected 12."); | |
| } | |
| } | |
| Debug.Log("Benchmark file: " + path); | |
| Debug.Log("File size: " + FormatBytes(fileSize)); | |
| Warmup(path); | |
| BenchmarkResult r1 = Measure("File.ReadAllBytes", path, measuredRuns, LoadWithReadAllBytes); | |
| BenchmarkResult r2 = Measure("FileStream.Read", path, measuredRuns, LoadWithFileStream); | |
| BenchmarkResult r3 = Measure("AsyncReadManager.Read", path, measuredRuns, LoadWithAsyncReadManager); | |
| StringBuilder sb = new StringBuilder(); | |
| sb.AppendLine("Load benchmark results"); | |
| sb.AppendLine("----------------------"); | |
| AppendResult(sb, r1); | |
| AppendResult(sb, r2); | |
| AppendResult(sb, r3); | |
| Debug.Log(sb.ToString()); | |
| } | |
| private string ResolvePath() | |
| { | |
| if (string.IsNullOrWhiteSpace(filePath)) | |
| return string.Empty; | |
| if (Path.IsPathRooted(filePath)) | |
| return filePath; | |
| if (useStreamingAssetsPath) | |
| return Path.Combine(Application.streamingAssetsPath, filePath); | |
| return Path.Combine(Application.dataPath, filePath); | |
| } | |
| private void Warmup(string path) | |
| { | |
| for (int i = 0; i < warmupRuns; i++) | |
| { | |
| LoadWithReadAllBytes(path); | |
| LoadWithFileStream(path); | |
| LoadWithAsyncReadManager(path); | |
| } | |
| } | |
| private delegate ulong LoadMethod(string path); | |
| private BenchmarkResult Measure(string name, string path, int runs, LoadMethod method) | |
| { | |
| long bytes = new FileInfo(path).Length; | |
| double totalMs = 0.0; | |
| ulong checksum = 0; | |
| for (int i = 0; i < runs; i++) | |
| { | |
| GC.Collect(); | |
| GC.WaitForPendingFinalizers(); | |
| GC.Collect(); | |
| Stopwatch sw = Stopwatch.StartNew(); | |
| checksum ^= method(path); | |
| sw.Stop(); | |
| totalMs += sw.Elapsed.TotalMilliseconds; | |
| } | |
| double avgMs = totalMs / runs; | |
| double avgMBps = (bytes / (1024.0 * 1024.0)) / (avgMs / 1000.0); | |
| return new BenchmarkResult | |
| { | |
| Name = name, | |
| Runs = runs, | |
| BytesRead = bytes, | |
| TotalMs = totalMs, | |
| AvgMs = avgMs, | |
| AvgMBps = avgMBps, | |
| Checksum = checksum | |
| }; | |
| } | |
| private unsafe ulong LoadWithReadAllBytes(string path) | |
| { | |
| byte[] bytes = File.ReadAllBytes(path); | |
| if (!forceConsumeData) | |
| return 0; | |
| fixed (byte* ptr = bytes) | |
| { | |
| return ComputeChecksum(ptr, bytes.Length); | |
| } | |
| } | |
| private unsafe ulong LoadWithFileStream(string path) | |
| { | |
| long fileSizeLong = new FileInfo(path).Length; | |
| int fileSize = checked((int)fileSizeLong); | |
| byte[] bytes = new byte[fileSize]; | |
| using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 1024 * 64, FileOptions.SequentialScan)) | |
| { | |
| int offset = 0; | |
| while (offset < fileSize) | |
| { | |
| int read = fs.Read(bytes, offset, fileSize - offset); | |
| if (read == 0) | |
| throw new EndOfStreamException("Unexpected end of file."); | |
| offset += read; | |
| } | |
| } | |
| if (!forceConsumeData) | |
| return 0; | |
| fixed (byte* ptr = bytes) | |
| { | |
| return ComputeChecksum(ptr, bytes.Length); | |
| } | |
| } | |
| private unsafe ulong LoadWithAsyncReadManager(string path) | |
| { | |
| long fileSizeLong = new FileInfo(path).Length; | |
| int fileSize = checked((int)fileSizeLong); | |
| byte* buffer = (byte*)UnsafeUtility.Malloc(fileSize, 16, Allocator.TempJob); | |
| ReadHandle handle = default; | |
| try | |
| { | |
| ReadCommand cmd = new ReadCommand | |
| { | |
| Offset = 0, | |
| Size = fileSize, | |
| Buffer = buffer | |
| }; | |
| handle = AsyncReadManager.Read(path, &cmd, 1); | |
| handle.JobHandle.Complete(); | |
| if (handle.Status != ReadStatus.Complete) | |
| throw new IOException("AsyncReadManager read failed. Status: " + handle.Status); | |
| if (!forceConsumeData) | |
| return 0; | |
| return ComputeChecksum(buffer, fileSize); | |
| } | |
| finally | |
| { | |
| if (handle.IsValid()) | |
| handle.Dispose(); | |
| if (buffer != null) | |
| UnsafeUtility.Free(buffer, Allocator.TempJob); | |
| } | |
| } | |
| private unsafe ulong ComputeChecksum(byte* ptr, int length) | |
| { | |
| ulong hash = 1469598103934665603UL; | |
| const ulong prime = 1099511628211UL; | |
| int stride = 64; | |
| for (int i = 0; i < length; i += stride) | |
| { | |
| hash ^= ptr[i]; | |
| hash *= prime; | |
| } | |
| if (length > 0) | |
| { | |
| hash ^= ptr[length - 1]; | |
| hash *= prime; | |
| } | |
| return hash; | |
| } | |
| private void AppendResult(StringBuilder sb, BenchmarkResult r) | |
| { | |
| sb.AppendLine(r.Name); | |
| sb.AppendLine(" Runs: " + r.Runs); | |
| sb.AppendLine(" Avg time: " + r.AvgMs.ToString("F3") + " ms"); | |
| sb.AppendLine(" Total time: " + r.TotalMs.ToString("F3") + " ms"); | |
| sb.AppendLine(" Avg speed: " + r.AvgMBps.ToString("F2") + " MB/s"); | |
| sb.AppendLine(" Data size: " + FormatBytes(r.BytesRead)); | |
| sb.AppendLine(" Checksum: " + r.Checksum); | |
| } | |
| private string FormatBytes(long bytes) | |
| { | |
| double mb = bytes / (1024.0 * 1024.0); | |
| return bytes + " bytes (" + mb.ToString("F2") + " MB)"; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment