Skip to content

Instantly share code, notes, and snippets.

@unitycoder
Created March 14, 2026 20:04
Show Gist options
  • Select an option

  • Save unitycoder/feee34ad09f8ffa7da73ce274cd1d8aa to your computer and use it in GitHub Desktop.

Select an option

Save unitycoder/feee34ad09f8ffa7da73ce274cd1d8aa to your computer and use it in GitHub Desktop.
Test DirectStorage loader in unity
// 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