Aucune description
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

RewindableAllocator.cs 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. using AOT;
  2. using System;
  3. using System.Runtime.CompilerServices;
  4. using System.Threading;
  5. using UnityEngine.Assertions;
  6. using Unity.Burst;
  7. using Unity.Collections.LowLevel.Unsafe;
  8. using Unity.Jobs.LowLevel.Unsafe;
  9. using Unity.Mathematics;
  10. namespace Unity.Collections
  11. {
  12. internal struct UnmanagedArray<T> : IDisposable where T : unmanaged
  13. {
  14. IntPtr m_pointer;
  15. int m_length;
  16. public int Length => m_length;
  17. AllocatorManager.AllocatorHandle m_allocator;
  18. public UnmanagedArray(int length, AllocatorManager.AllocatorHandle allocator)
  19. {
  20. unsafe
  21. {
  22. m_pointer = (IntPtr)Memory.Unmanaged.Array.Allocate<T>(length, allocator);
  23. }
  24. m_length = length;
  25. m_allocator = allocator;
  26. }
  27. public void Dispose()
  28. {
  29. unsafe
  30. {
  31. Memory.Unmanaged.Free((T*)m_pointer, Allocator.Persistent);
  32. }
  33. }
  34. public unsafe T* GetUnsafePointer()
  35. {
  36. return (T*)m_pointer;
  37. }
  38. public ref T this[int index]
  39. {
  40. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  41. get { unsafe { return ref ((T*)m_pointer)[index]; } }
  42. }
  43. }
  44. /// <summary>
  45. /// An allocator that is fast like a linear allocator, is threadsafe, and automatically invalidates
  46. /// all allocations made from it, when "rewound" by the user.
  47. /// </summary>
  48. [BurstCompile]
  49. public struct RewindableAllocator : AllocatorManager.IAllocator
  50. {
  51. internal struct Union
  52. {
  53. internal long m_long;
  54. // Number of bits used to store current position in a block to give out memory.
  55. // This limits the maximum block size to 1TB (2^40).
  56. const int currentBits = 40;
  57. // Offset of current position in m_long
  58. const int currentOffset = 0;
  59. // Number of bits used to store the allocation count in a block
  60. const long currentMask = (1L << currentBits) - 1;
  61. // Number of bits used to store allocation count in a block.
  62. // This limits the maximum number of allocations per block to 16 millions (2^24)
  63. const int allocCountBits = 24;
  64. // Offset of allocation count in m_long
  65. const int allocCountOffset = currentOffset + currentBits;
  66. const long allocCountMask = (1L << allocCountBits) - 1;
  67. // Current position in a block to give out memory
  68. internal long m_current
  69. {
  70. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  71. get
  72. {
  73. return (m_long >> currentOffset) & currentMask;
  74. }
  75. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  76. set
  77. {
  78. m_long &= ~(currentMask << currentOffset);
  79. m_long |= (value & currentMask) << currentOffset;
  80. }
  81. }
  82. // The number of allocations in a block
  83. internal long m_allocCount
  84. {
  85. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  86. get
  87. {
  88. return (m_long >> allocCountOffset) & allocCountMask;
  89. }
  90. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  91. set
  92. {
  93. m_long &= ~(allocCountMask << allocCountOffset);
  94. m_long |= (value & allocCountMask) << allocCountOffset;
  95. }
  96. }
  97. }
  98. [GenerateTestsForBurstCompatibility]
  99. internal unsafe struct MemoryBlock : IDisposable
  100. {
  101. // can't align any coarser than this many bytes
  102. public const int kMaximumAlignment = 16384;
  103. // pointer to contiguous memory
  104. public byte* m_pointer;
  105. // how many bytes of contiguous memory it points to
  106. public long m_bytes;
  107. // Union of current position to give out memory and allocation counts
  108. public Union m_union;
  109. public MemoryBlock(long bytes)
  110. {
  111. m_pointer = (byte*)Memory.Unmanaged.Allocate(bytes, kMaximumAlignment, Allocator.Persistent);
  112. Assert.IsTrue(m_pointer != null, "Memory block allocation failed, system out of memory");
  113. m_bytes = bytes;
  114. m_union = default;
  115. }
  116. public void Rewind()
  117. {
  118. m_union = default;
  119. }
  120. public void Dispose()
  121. {
  122. Memory.Unmanaged.Free(m_pointer, Allocator.Persistent);
  123. m_pointer = null;
  124. m_bytes = 0;
  125. m_union = default;
  126. }
  127. public bool Contains(IntPtr ptr)
  128. {
  129. unsafe
  130. {
  131. void* pointer = (void*)ptr;
  132. return (pointer >= m_pointer) && (pointer < m_pointer + m_union.m_current);
  133. }
  134. }
  135. };
  136. // Log2 of Maximum memory block size. Cannot exceed MemoryBlock.Union.currentBits.
  137. const int kLog2MaxMemoryBlockSize = 26;
  138. // Maximum memory block size. Can exceed maximum memory block size if user requested more.
  139. const long kMaxMemoryBlockSize = 1L << kLog2MaxMemoryBlockSize; // 64MB
  140. /// Minimum memory block size, 128KB.
  141. const long kMinMemoryBlockSize = 128 * 1024;
  142. /// Maximum number of memory blocks.
  143. const int kMaxNumBlocks = 64;
  144. // Bit mask (bit 31) of the memory block busy flag indicating whether the block is busy rewinding.
  145. const int kBlockBusyRewindMask = 0x1 << 31;
  146. // Bit mask of the memory block busy flag indicating whether the block is busy allocating.
  147. const int kBlockBusyAllocateMask = ~kBlockBusyRewindMask;
  148. Spinner m_spinner;
  149. AllocatorManager.AllocatorHandle m_handle;
  150. UnmanagedArray<MemoryBlock> m_block;
  151. int m_last; // highest-index block that has memory to allocate from
  152. int m_used; // highest-index block that we actually allocated from, since last rewind
  153. byte m_enableBlockFree; // flag indicating if allocator enables individual block free
  154. byte m_reachMaxBlockSize; // flag indicating if reach maximum block size
  155. /// <summary>
  156. /// Initializes the allocator. Must be called before first use.
  157. /// </summary>
  158. /// <param name="initialSizeInBytes">The initial capacity of the allocator, in bytes</param>
  159. /// <param name="enableBlockFree">A flag indicating if allocator enables individual block free</param>
  160. public void Initialize(int initialSizeInBytes, bool enableBlockFree = false)
  161. {
  162. m_spinner = default;
  163. m_block = new UnmanagedArray<MemoryBlock>(kMaxNumBlocks, Allocator.Persistent);
  164. // Initial block size should be larger than min block size
  165. var blockSize = initialSizeInBytes > kMinMemoryBlockSize ? initialSizeInBytes : kMinMemoryBlockSize;
  166. m_block[0] = new MemoryBlock(blockSize);
  167. m_last = m_used = 0;
  168. m_enableBlockFree = enableBlockFree ? (byte)1 : (byte)0;
  169. m_reachMaxBlockSize = (initialSizeInBytes >= kMaxMemoryBlockSize) ? (byte)1 : (byte)0;
  170. }
  171. /// <summary>
  172. /// Property to get and set enable block free flag, a flag indicating whether the allocator should enable individual block to be freed.
  173. /// </summary>
  174. public bool EnableBlockFree
  175. {
  176. get => m_enableBlockFree != 0;
  177. set => m_enableBlockFree = value ? (byte)1 : (byte)0;
  178. }
  179. /// <summary>
  180. /// Retrieves the number of memory blocks that the allocator has requested from the system.
  181. /// </summary>
  182. public int BlocksAllocated => (int)(m_last + 1);
  183. /// <summary>
  184. /// Retrieves the size of the initial memory block, as requested in the Initialize function.
  185. /// </summary>
  186. public int InitialSizeInBytes => (int)(m_block[0].m_bytes);
  187. /// <summary>
  188. /// Retrieves the maximum memory block size.
  189. /// </summary>
  190. internal long MaxMemoryBlockSize => kMaxMemoryBlockSize;
  191. /// <summary>
  192. /// Retrieves the total bytes of the memory blocks allocated by this allocator.
  193. /// </summary>
  194. internal long BytesAllocated
  195. {
  196. get
  197. {
  198. long totalBytes = 0;
  199. for(int i = 0; i <= m_last; i++)
  200. {
  201. totalBytes += m_block[i].m_bytes;
  202. }
  203. return totalBytes;
  204. }
  205. }
  206. /// <summary>
  207. /// Rewind the allocator; invalidate all allocations made from it, and potentially also free memory blocks
  208. /// it has allocated from the system.
  209. /// </summary>
  210. public void Rewind()
  211. {
  212. if (JobsUtility.IsExecutingJob)
  213. throw new InvalidOperationException("You cannot Rewind a RewindableAllocator from a Job.");
  214. m_handle.Rewind(); // bump the allocator handle version, invalidate all dependents
  215. while (m_last > m_used) // *delete* all blocks we didn't even allocate from this time around.
  216. m_block[m_last--].Dispose();
  217. while (m_used > 0) // simply *rewind* all blocks we used in this update, to avoid allocating again, every update.
  218. m_block[m_used--].Rewind();
  219. m_block[0].Rewind();
  220. }
  221. /// <summary>
  222. /// Dispose the allocator. This must be called to free the memory blocks that were allocated from the system.
  223. /// </summary>
  224. public void Dispose()
  225. {
  226. if (JobsUtility.IsExecutingJob)
  227. throw new InvalidOperationException("You cannot Dispose a RewindableAllocator from a Job.");
  228. m_used = 0; // so that we delete all blocks in Rewind() on the next line
  229. Rewind();
  230. m_block[0].Dispose();
  231. m_block.Dispose();
  232. m_last = m_used = 0;
  233. }
  234. /// <summary>
  235. /// All allocators must implement this property, in order to be installed in the custom allocator table.
  236. /// </summary>
  237. [ExcludeFromBurstCompatTesting("Uses managed delegate")]
  238. public AllocatorManager.TryFunction Function => Try;
  239. unsafe int TryAllocate(ref AllocatorManager.Block block, int startIndex, int lastIndex, long alignedSize, long alignmentMask)
  240. {
  241. for (int best = startIndex; best <= lastIndex; best++)
  242. {
  243. Union oldUnion;
  244. Union readUnion = default;
  245. long begin = 0;
  246. bool skip = false;
  247. readUnion.m_long = Interlocked.Read(ref m_block[best].m_union.m_long);
  248. do
  249. {
  250. begin = (readUnion.m_current + alignmentMask) & ~alignmentMask;
  251. if (begin + block.Bytes > m_block[best].m_bytes)
  252. {
  253. skip = true;
  254. break;
  255. }
  256. oldUnion = readUnion;
  257. Union newUnion = default;
  258. newUnion.m_current = (begin + alignedSize) > m_block[best].m_bytes ? m_block[best].m_bytes : (begin + alignedSize);
  259. newUnion.m_allocCount = readUnion.m_allocCount + 1;
  260. readUnion.m_long = Interlocked.CompareExchange(ref m_block[best].m_union.m_long, newUnion.m_long, oldUnion.m_long);
  261. } while (readUnion.m_long != oldUnion.m_long);
  262. if(skip)
  263. {
  264. continue;
  265. }
  266. block.Range.Pointer = (IntPtr)(m_block[best].m_pointer + begin);
  267. block.AllocatedItems = block.Range.Items;
  268. Interlocked.MemoryBarrier();
  269. int oldUsed;
  270. int readUsed;
  271. int newUsed;
  272. readUsed = m_used;
  273. do
  274. {
  275. oldUsed = readUsed;
  276. newUsed = best > oldUsed ? best : oldUsed;
  277. readUsed = Interlocked.CompareExchange(ref m_used, newUsed, oldUsed);
  278. } while (newUsed != oldUsed);
  279. return AllocatorManager.kErrorNone;
  280. }
  281. return AllocatorManager.kErrorBufferOverflow;
  282. }
  283. /// <summary>
  284. /// Try to allocate, free, or reallocate a block of memory. This is an internal function, and
  285. /// is not generally called by the user.
  286. /// </summary>
  287. /// <param name="block">The memory block to allocate, free, or reallocate</param>
  288. /// <returns>0 if successful. Otherwise, returns the error code from the allocator function.</returns>
  289. public int Try(ref AllocatorManager.Block block)
  290. {
  291. if (block.Range.Pointer == IntPtr.Zero)
  292. {
  293. // Make the alignment multiple of cacheline size
  294. var alignment = math.max(JobsUtility.CacheLineSize, block.Alignment);
  295. var extra = alignment != JobsUtility.CacheLineSize ? 1 : 0;
  296. var cachelineMask = JobsUtility.CacheLineSize - 1;
  297. if (extra == 1)
  298. {
  299. alignment = (alignment + cachelineMask) & ~cachelineMask;
  300. }
  301. // Adjust the size to be multiple of alignment, add extra alignment
  302. // to size if alignment is more than cacheline size
  303. var mask = alignment - 1L;
  304. var size = (block.Bytes + extra * alignment + mask) & ~mask;
  305. // Check all the blocks to see if any of them have enough memory
  306. var last = m_last;
  307. int error = TryAllocate(ref block, 0, m_last, size, mask);
  308. if (error == AllocatorManager.kErrorNone)
  309. {
  310. return error;
  311. }
  312. // If that fails, allocate another block that's guaranteed big enough, and allocate from it.
  313. // Allocate twice as much as last time until it reaches MaxMemoryBlockSize, after that, increase
  314. // the block size by MaxMemoryBlockSize.
  315. m_spinner.Acquire();
  316. // After getting the lock, we must try to allocate again, because if many threads waited at
  317. // the lock, the first one allocates and when it unlocks, it's likely that there's space for the
  318. // other threads' allocations in the first thread's block.
  319. error = TryAllocate(ref block, last, m_last, size, mask);
  320. if (error == AllocatorManager.kErrorNone)
  321. {
  322. m_spinner.Release();
  323. return error;
  324. }
  325. long bytes;
  326. if (m_reachMaxBlockSize == 0)
  327. {
  328. bytes = m_block[m_last].m_bytes << 1;
  329. }
  330. else
  331. {
  332. bytes = m_block[m_last].m_bytes + kMaxMemoryBlockSize;
  333. }
  334. // if user asks more, skip smaller sizes
  335. bytes = math.max(bytes, size);
  336. m_reachMaxBlockSize = (bytes >= kMaxMemoryBlockSize) ? (byte)1 : (byte)0;
  337. m_block[m_last + 1] = new MemoryBlock(bytes);
  338. Interlocked.Increment(ref m_last);
  339. error = TryAllocate(ref block, m_last, m_last, size, mask);
  340. m_spinner.Release();
  341. return error;
  342. }
  343. // To free memory, no-op unless allocator enables individual block to be freed
  344. if (block.Range.Items == 0)
  345. {
  346. if (m_enableBlockFree != 0)
  347. {
  348. for (int blockIndex = 0; blockIndex <= m_last; ++blockIndex)
  349. {
  350. if (m_block[blockIndex].Contains(block.Range.Pointer))
  351. {
  352. Union oldUnion;
  353. Union readUnion = default;
  354. readUnion.m_long = Interlocked.Read(ref m_block[blockIndex].m_union.m_long);
  355. do
  356. {
  357. oldUnion = readUnion;
  358. Union newUnion = readUnion;
  359. newUnion.m_allocCount--;
  360. if (newUnion.m_allocCount == 0)
  361. {
  362. newUnion.m_current = 0;
  363. }
  364. readUnion.m_long = Interlocked.CompareExchange(ref m_block[blockIndex].m_union.m_long, newUnion.m_long, oldUnion.m_long);
  365. } while (readUnion.m_long != oldUnion.m_long);
  366. }
  367. }
  368. }
  369. return 0; // we could check to see if the pointer belongs to us, if we want to be strict about it.
  370. }
  371. return -1;
  372. }
  373. [BurstCompile]
  374. [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))]
  375. internal static int Try(IntPtr state, ref AllocatorManager.Block block)
  376. {
  377. unsafe { return ((RewindableAllocator*)state)->Try(ref block); }
  378. }
  379. /// <summary>
  380. /// Retrieve the AllocatorHandle associated with this allocator. The handle is used as an index into a
  381. /// global table, for times when a reference to the allocator object isn't available.
  382. /// </summary>
  383. /// <value>The AllocatorHandle retrieved.</value>
  384. public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } }
  385. /// <summary>
  386. /// Retrieve the Allocator associated with this allocator.
  387. /// </summary>
  388. /// <value>The Allocator retrieved.</value>
  389. public Allocator ToAllocator { get { return m_handle.ToAllocator; } }
  390. /// <summary>
  391. /// Check whether this AllocatorHandle is a custom allocator.
  392. /// </summary>
  393. /// <value>True if this AllocatorHandle is a custom allocator.</value>
  394. public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } }
  395. /// <summary>
  396. /// Check whether this allocator will automatically dispose allocations.
  397. /// </summary>
  398. /// <remarks>Allocations made by Rewindable allocator are automatically disposed.</remarks>
  399. /// <value>Always true</value>
  400. public bool IsAutoDispose { get { return true; } }
  401. /// <summary>
  402. /// Allocate a NativeArray of type T from memory that is guaranteed to remain valid until the end of the
  403. /// next Update of this World. There is no need to Dispose the NativeArray so allocated. It is not possible
  404. /// to free the memory by Disposing it - it is automatically freed after the end of the next Update for this
  405. /// World.
  406. /// </summary>
  407. /// <typeparam name="T">The element type of the NativeArray to allocate.</typeparam>
  408. /// <param name="length">The length of the NativeArray to allocate, measured in elements.</param>
  409. /// <returns>The NativeArray allocated by this function.</returns>
  410. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
  411. public NativeArray<T> AllocateNativeArray<T>(int length) where T : unmanaged
  412. {
  413. var container = new NativeArray<T>();
  414. unsafe
  415. {
  416. container.m_Buffer = this.AllocateStruct(default(T), length);
  417. }
  418. container.m_Length = length;
  419. container.m_AllocatorLabel = Allocator.None;
  420. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  421. container.m_MinIndex = 0;
  422. container.m_MaxIndex = length - 1;
  423. container.m_Safety = CollectionHelper.CreateSafetyHandle(ToAllocator);
  424. CollectionHelper.SetStaticSafetyId<NativeArray<T>>(ref container.m_Safety, ref NativeArrayExtensions.NativeArrayStaticId<T>.s_staticSafetyId.Data);
  425. Handle.AddSafetyHandle(container.m_Safety);
  426. #endif
  427. return container;
  428. }
  429. /// <summary>
  430. /// Allocate a NativeList of type T from memory that is guaranteed to remain valid until the end of the
  431. /// next Update of this World. There is no need to Dispose the NativeList so allocated. It is not possible
  432. /// to free the memory by Disposing it - it is automatically freed after the end of the next Update for this
  433. /// World. The NativeList must be initialized with its maximum capacity; if it were to dynamically resize,
  434. /// up to 1/2 of the total final capacity would be wasted, because the memory can't be dynamically freed.
  435. /// </summary>
  436. /// <typeparam name="T">The element type of the NativeList to allocate.</typeparam>
  437. /// <param name="capacity">The capacity of the NativeList to allocate, measured in elements.</param>
  438. /// <returns>The NativeList allocated by this function.</returns>
  439. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })]
  440. public NativeList<T> AllocateNativeList<T>(int capacity) where T : unmanaged
  441. {
  442. var container = new NativeList<T>();
  443. unsafe
  444. {
  445. container.m_ListData = this.Allocate(default(UnsafeList<T>), 1);
  446. container.m_ListData->Ptr = this.Allocate(default(T), capacity);
  447. container.m_ListData->m_length = 0;
  448. container.m_ListData->m_capacity = capacity;
  449. container.m_ListData->Allocator = Allocator.None;
  450. }
  451. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  452. container.m_Safety = CollectionHelper.CreateSafetyHandle(ToAllocator);
  453. CollectionHelper.SetStaticSafetyId<NativeList<T>>(ref container.m_Safety, ref NativeList<T>.s_staticSafetyId.Data);
  454. AtomicSafetyHandle.SetBumpSecondaryVersionOnScheduleWrite(container.m_Safety, true);
  455. Handle.AddSafetyHandle(container.m_Safety);
  456. #endif
  457. return container;
  458. }
  459. }
  460. }