简体   繁体   中英

How to write to file with a pointer (byte*) to an array of byte

I am saving too many files large files for a mobile OS, so I am assuming I should be use byte* for saving memory and allocations

Example Code

private unsafe static void WriteMesh(ref MeshInfo mesh, BinaryWriter bw)
{
    var size = UnsafeUtility.SizeOf<float3>() * mesh.vertices.Length;
    byte* pByte = (byte*)mesh.vertices.GetUnsafePtr();

    bw.Write(pByte); // Obviously this wont work
}

I know I can do this with Span<T> , but I am in Unity which doesn't support it. Is there another way?

Disregarding any other problems (conceptual or otherwise). There are a few ways to do this.

Convoluted examples ensue

If you could use Span<T> which can take a pointer and length and then use FileStream.Write(ReadOnlySpan<Byte>) overload

Writes a sequence of bytes from a read-only span to the current file stream and advances the current position within this file stream by the number of bytes written.

var bytes = new byte[] {1,2,3};
var size = bytes.Length;

using var fs = new FileStream(@"SomeAwesomeFileNamedBob.dat", FileMode.Create);

fixed (byte* p = bytes)
{
   var span = new Span<byte>(p, size);
   fs.Write(span);
}

Or, just use BinaryWriter.Write and write each byte, this is a̶ ̶l̶i̶t̶t̶l̶e̶... extremely inefficient

Writes a signed byte to the current stream and advances the stream position by one byte.

var bytes = new byte[] {1, 2, 3};
var size = bytes.Length;

using var fs = new FileStream(@"SomeAwesomeFileNamedBob.dat", FileMode.Create);
using var bw = new BinaryWriter(fs);

fixed (byte* p = bytes)
   for (int i = 0; i < size; i++)
      bw.Write(*p);

Or, at the cost of an allocation , just Buffer.MemoryCopy to a new array and Write

Copies a block of memory.

var bytes = new byte[] {1,2,3};
var size = bytes.Length;

using var fs = new FileStream(@"SomeAwesomeFileNamedBob.dat", FileMode.Create);

var temp = new byte[size];
fixed (byte* pOld = bytes,pNew = temp)
{
   Buffer.MemoryCopy(pOld,pNew,size,size);
   fs.Write(temp,0,size);
}

Or, expanding on the array copy method, you could use an ArrayPool<Byte> for fewer allocations and in-turn will be better for your LOH (if applicable)

Provides a resource pool that enables reusing instances of type T[].

private static readonly ArrayPool<byte> _pool = ArrayPool<byte>.Shared;

...

var size = bytes.Length;

using var fs = new FileStream(@"SomeAwesomeFileNamedBob.dat", FileMode.Create);

var temp = _pool.Rent(size);
try
{
   fixed (byte* pOld = bytes, pNew = temp)
   {
      Buffer.MemoryCopy(pOld, pNew, size, size);
      fs.Write(temp, 0, size);
   }
}
finally
{
   _pool.Return(temp);
}

Or you could use an UnmanagedMemoryStream

Provides access to unmanaged blocks of memory from managed code.

Important

This API is not CLS-compliant.

var bytes = new byte[] {1,2,3};
var size = bytes.Length;

using var fs = new FileStream(@"SomeAwesomeFileNamedBob.dat", FileMode.Create);

fixed (byte* p = bytes)
{
   using var us = new UnmanagedMemoryStream(p,size);
   us.CopyTo(fs);
}

Benchmarks

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.630 (2004/?/20H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.100
  [Host]        : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT  [AttachedDebugger]
  .NET Core 5.0 : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT

Job=.NET Core 5.0  Runtime=.NET Core 5.0

|          Method |  _size |         Mean |        Error |       StdDev |       Median |
|---------------- |------- |-------------:|-------------:|-------------:|-------------:|
|            Span |   1000 |     122.4 ns |      2.46 ns |      2.42 ns |     122.7 ns |
|          Single |   1000 |   5,548.3 ns |     82.61 ns |     73.23 ns |   5,561.8 ns |
|        NewArray |   1000 |     230.4 ns |      4.64 ns |     10.56 ns |     227.4 ns |
|       ArrayPool |   1000 |     185.6 ns |      3.74 ns |      4.60 ns |     186.1 ns |
| UnmanagedStream |   1000 |     249.8 ns |      4.89 ns |      8.69 ns |     247.5 ns |
|---------------- |------- |-------------:|-------------:|-------------:|-------------:|
|            Span |  10000 |   1,012.9 ns |     20.06 ns |     44.87 ns |   1,007.0 ns |
|          Single |  10000 |  56,143.2 ns |    980.01 ns |  1,436.48 ns |  56,087.6 ns |
|        NewArray |  10000 |   2,086.1 ns |     43.89 ns |    127.34 ns |   2,048.9 ns |
|       ArrayPool |  10000 |   1,277.2 ns |     24.38 ns |     50.88 ns |   1,272.3 ns |
| UnmanagedStream |  10000 |   1,267.8 ns |     24.52 ns |     28.24 ns |   1,260.9 ns |
|---------------- |------- |-------------:|-------------:|-------------:|-------------:|
|            Span | 100000 |  56,843.0 ns |  1,107.92 ns |  1,137.75 ns |  56,587.5 ns |
|          Single | 100000 | 601,186.9 ns | 11,991.48 ns | 17,576.95 ns | 598,002.9 ns |
|        NewArray | 100000 | 111,234.1 ns |  1,296.51 ns |  1,012.23 ns | 111,268.3 ns |
|       ArrayPool | 100000 |  59,183.1 ns |    278.01 ns |    232.15 ns |  59,141.8 ns |
| UnmanagedStream | 100000 |  58,539.6 ns |    941.79 ns |    834.87 ns |  58,176.1 ns |

Setup

[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public unsafe class DumbTest
{
   [Params(1000, 10000, 100000)] public int _size;

   private byte* _p;
   private GCHandle _handle;
   private readonly ArrayPool<byte> _pool = ArrayPool<byte>.Shared;

   [GlobalSetup]
   public void Setup()
   {
      var bytes = new byte[_size];
      new Random(42).NextBytes(bytes);
      _handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
      _p = (byte*) _handle.AddrOfPinnedObject();
   }

   [GlobalCleanup]
   public void Cleanup() => _handle.Free();

   [Benchmark]
   public void Span()
   {
      using var ms = new MemoryStream();
      var span = new Span<byte>(_p, _size);
      ms.Write(span);
   }

   [Benchmark]
   public void Single()
   {
      using var ms = new MemoryStream();
      using var bw = new BinaryWriter(ms);
      for (var i = 0; i < _size; i++)
         bw.Write(*_p);
   }

   [Benchmark]
   public void NewArray()
   {
      using var ms = new MemoryStream();

      var temp = new byte[_size];
      fixed (byte* pNew = temp)
      {
         Buffer.MemoryCopy(_p, pNew, _size, _size);
         ms.Write(temp, 0, _size);
      }
   }

   [Benchmark]
   public void ArrayPool()
   {
      using var ms = new MemoryStream();

      var temp = _pool.Rent(_size);
      try
      {
         fixed (byte* pNew = temp)
         {
            Buffer.MemoryCopy(_p,pNew,_size,_size);
            ms.Write(temp,0,_size);
         }
      }
      finally
      {
         _pool.Return(temp);
      }
   }
   [Benchmark]
   public void UnmanagedStream()
   {
      using var ms = new MemoryStream();
      using var us = new UnmanagedMemoryStream(_p, _size);
      us.CopyTo(ms);

   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM