diff --git a/JacksonDunstanNativeCollections/NativePerJobThreadIntPtrs.cs b/JacksonDunstanNativeCollections/NativePerJobThreadIntPtrs.cs
new file mode 100644
index 0000000..7bb5903
--- /dev/null
+++ b/JacksonDunstanNativeCollections/NativePerJobThreadIntPtrs.cs
@@ -0,0 +1,449 @@
+//-----------------------------------------------------------------------
+// 
+//     Copyright (c) Jackson Dunstan. See LICENSE.txt.
+// 
+//-----------------------------------------------------------------------
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Unity.Burst;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Jobs.LowLevel.Unsafe;
+
+namespace JacksonDunstan.NativeCollections
+{
+	/// 
+	/// A pointer to an int array stored in native (i.e. unmanaged) memory. One
+	/// integer array is stored for each of the maximum number of job threads.
+	/// As of Unity 2018.2, this results in 8 KB of memory usage for every 16
+	/// integers. The advantage over  is that all
+	/// operations on  are faster due to not being
+	/// atomic. The resulting array ints are collected with a loop. This is
+	/// therefore a good option when most usage is via 
+	/// and memory usage is not a concern.
+	/// 
+	[NativeContainer]
+	[NativeContainerSupportsDeallocateOnJobCompletion]
+	[DebuggerTypeProxy(typeof(NativePerJobThreadIntPtrsDebugView))]
+	[DebuggerDisplay("Value = NativeArray")]
+	[StructLayout(LayoutKind.Sequential)]
+	public unsafe struct NativePerJobThreadIntPtrs : IDisposable
+	{
+		/// 
+		/// An atomic write-only version of the object suitable for use in a
+		/// ParallelFor job
+		/// 
+		[NativeContainer]
+		[NativeContainerIsAtomicWriteOnly]
+		public struct Parallel
+		{
+
+			/// 
+			/// The number of integers stored in the array.
+			/// 
+			public readonly int Length;
+
+			/// 
+			/// Pointers to the integer values in native memory
+			/// 
+			[NativeDisableUnsafePtrRestriction]
+			internal int* m_Buffer;
+
+			/// 
+			/// Thread index of the job using this object. This is set by Unity
+			/// and must have this exact name and type.
+			/// 
+			[NativeSetThreadIndex]
+			internal int m_ThreadIndex;
+
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+			/// 
+			/// A handle to information about what operations can be safely
+			/// performed on the object at any given time.
+			/// 
+			internal AtomicSafetyHandle m_Safety;
+
+			/// 
+			/// Create a parallel version of the object
+			/// 
+			/// 
+			/// 
+			/// The number of integers stored in the array
+			/// 
+			/// 
+			/// 
+			/// Pointer to the value
+			/// 
+			/// 
+			/// 
+			/// Atomic safety handle for the object
+			/// 
+			internal Parallel(int length, int* value, AtomicSafetyHandle safety)
+			{
+				Length = length;
+				m_Buffer = value;
+				m_ThreadIndex = 0;
+				m_Safety = safety;
+			}
+#else
+			/// 
+			/// Create a parallel version of the object
+			/// 
+			/// 
+			/// 
+			/// The number of integers stored in the array
+			/// 
+			/// 
+			/// 
+			/// Pointer to the value
+			/// 
+			internal Parallel(int length, int* value)
+			{
+				Length = length;
+				m_Buffer = value;
+				m_ThreadIndex = 0;
+			}
+#endif
+
+			/// 
+			/// Increment the stored value
+			/// 
+			[WriteAccessRequired]
+			public void Increment(int index)
+			{
+				RequireWriteAccess();
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+				if (index < 0 || index >= Length) throw new ArgumentException("Index must be between 0 and Length - 1.", nameof(index));
+#endif
+				m_Buffer[IntsPerCacheLine * m_ThreadIndex + index]++;
+			}
+
+			/// 
+			/// Decrement the stored value
+			/// 
+			[WriteAccessRequired]
+			public void Decrement(int index)
+			{
+				RequireWriteAccess();
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+				if (index < 0 || index >= Length) throw new ArgumentException("Index must be between 0 and Length - 1.", nameof(index));
+#endif
+				m_Buffer[IntsPerCacheLine * m_ThreadIndex + index]--;
+			}
+
+			/// 
+			/// Add to the stored value
+			/// 
+			/// 
+			/// 
+			/// Value to add. Use negative values for subtraction.
+			/// 
+			[WriteAccessRequired]
+			public void Add(int index, int value)
+			{
+				RequireWriteAccess();
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+				if (index < 0 || index >= Length) throw new ArgumentException("Index must be between 0 and Length - 1.", nameof(index));
+#endif
+				m_Buffer[IntsPerCacheLine * m_ThreadIndex + index] += value;
+			}
+
+			/// 
+			/// Throw an exception if the object isn't writable
+			/// 
+			[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
+			private void RequireWriteAccess()
+			{
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+				AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
+#endif
+			}
+		}
+
+		/// 
+		/// The number of integers stored in the array.
+		/// 
+		public readonly int Length;
+
+		/// 
+		/// Pointers to the integer values in native memory. Must be named
+		/// exactly this way to allow for
+		/// [NativeContainerSupportsDeallocateOnJobCompletion]
+		/// 
+		[NativeDisableUnsafePtrRestriction]
+		internal int* m_Buffer;
+
+		/// 
+		/// Allocator used to create the backing memory
+		/// 
+		/// This field must be named this way to comply with
+		/// [NativeContainerSupportsDeallocateOnJobCompletion]
+		/// 
+		internal Allocator m_AllocatorLabel;
+
+		// These fields are all required when safety checks are enabled
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+		/// 
+		/// A handle to information about what operations can be safely
+		/// performed on the object at any given time.
+		/// 
+		private AtomicSafetyHandle m_Safety;
+
+		/// 
+		/// A handle that can be used to tell if the object has been disposed
+		/// yet or not, which allows for error-checking double disposal.
+		/// 
+		[NativeSetClassTypeToNullOnSchedule]
+		private DisposeSentinel m_DisposeSentinel;
+#endif
+
+		/// 
+		/// The number of integers that fit into a CPU cache line
+		/// 
+		private const int IntsPerCacheLine = JobsUtility.CacheLineSize / sizeof(int);
+
+		/// 
+		/// The number of integers that fit into a CPU cache line
+		/// 
+		private readonly int NumCacheLines;
+
+		/// 
+		/// Allocate memory and set the initial value
+		/// 
+		/// 
+		/// 
+		/// The number of logical integers to allocate. Initial value is 0 for
+		/// all array elements.
+		/// 
+		/// 
+		/// 
+		/// Allocator to allocate and deallocate with. Must be valid.
+		/// 
+		public NativePerJobThreadIntPtrs(int length, Allocator allocator)
+		{
+			// Require a valid allocator
+			if (!UnsafeUtility.IsValidAllocator(allocator))
+			{
+				throw new ArgumentException(
+					"Allocator must be Temp, TempJob or Persistent",
+					"allocator");
+			}
+
+			// Need a multiple of the number of cache lines
+			Length = length;
+			NumCacheLines = (int)Math.Ceiling((double)length / IntsPerCacheLine);
+
+			// Allocate the memory for the values
+			int bufferSize = JobsUtility.CacheLineSize * NumCacheLines * JobsUtility.MaxJobThreadCount;
+			m_Buffer = (int*)UnsafeUtility.Malloc(
+				bufferSize,
+				UnsafeUtility.AlignOf(),
+				allocator);
+			UnsafeUtility.MemClear(m_Buffer, bufferSize);
+
+			// Store the allocator to use when deallocating
+			m_AllocatorLabel = allocator;
+
+			// Create the dispose sentinel
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+#if UNITY_2018_3_OR_NEWER
+			DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0, allocator);
+#else
+			DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 0);
+#endif
+#endif
+		}
+
+		/// 
+		/// Get or set the contained value at the provided index
+		/// 
+		/// This operation requires read access to the node for 'get' and write
+		/// access to the node for 'set'.
+		/// 
+		/// 
+		/// 
+		/// The contained value at the provided index.
+		/// 
+		public int this[int index]
+		{
+			get
+			{
+				RequireReadAccess();
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+				if (index < 0 || index >= Length) throw new ArgumentException("Index must be between 0 and Length - 1.", nameof(index));
+#endif
+				int value = 0;
+				for (int i = 0; i < JobsUtility.MaxJobThreadCount; ++i)
+				{
+					value += m_Buffer[IntsPerCacheLine * i + index];
+				}
+				return value;
+			}
+
+			[WriteAccessRequired]
+			set
+			{
+				RequireWriteAccess();
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+				if (index < 0 || index >= Length) throw new ArgumentException("Index must be between 0 and Length - 1.", nameof(index));
+#endif
+				*(m_Buffer + index) = value;
+				for (int i = 1; i < JobsUtility.MaxJobThreadCount; ++i)
+				{
+					m_Buffer[IntsPerCacheLine * i + index] = 0;
+				}
+			}
+		}
+
+		/// 
+		/// Get a version of this object suitable for use in a ParallelFor job
+		/// 
+		/// 
+		/// 
+		/// A version of this object suitable for use in a ParallelFor job
+		/// 
+		public Parallel GetParallel()
+		{
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+			Parallel parallel = new Parallel(Length, m_Buffer, m_Safety);
+			AtomicSafetyHandle.UseSecondaryVersion(ref parallel.m_Safety);
+#else
+			Parallel parallel = new Parallel(Length, m_Buffer);
+#endif
+			return parallel;
+		}
+
+		/// 
+		/// Check if the underlying unmanaged memory has been created and not
+		/// freed via a call to .
+		/// 
+		/// This operation has no access requirements.
+		///
+		/// This operation is O(1).
+		/// 
+		/// 
+		/// 
+		/// Initially true when a non-default constructor is called but
+		/// initially false when the default constructor is used. After
+		///  is called, this becomes false. Note that
+		/// calling  on one copy of this object doesn't
+		/// result in this becoming false for all copies if it was true before.
+		/// This property should not be used to check whether the object
+		/// is usable, only to check whether it was ever usable.
+		/// 
+		public bool IsCreated
+		{
+			get
+			{
+				return m_Buffer != null;
+			}
+		}
+
+		/// 
+		/// Release the object's unmanaged memory. Do not use it after this. Do
+		/// not call  on copies of the object either.
+		/// 
+		/// This operation requires write access.
+		/// 
+		/// This complexity of this operation is O(1) plus the allocator's
+		/// deallocation complexity.
+		/// 
+		[WriteAccessRequired]
+		public void Dispose()
+		{
+			RequireWriteAccess();
+
+// Make sure we're not double-disposing
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+#if UNITY_2018_3_OR_NEWER
+			DisposeSentinel.Dispose(ref m_Safety, ref m_DisposeSentinel);
+#else
+			DisposeSentinel.Dispose(m_Safety, ref m_DisposeSentinel);
+#endif
+#endif
+
+			UnsafeUtility.Free(m_Buffer, m_AllocatorLabel);
+			m_Buffer = null;
+		}
+
+		/// 
+		/// Set whether both read and write access should be allowed. This is
+		/// used for automated testing purposes only.
+		/// 
+		/// 
+		/// 
+		/// If both read and write access should be allowed
+		/// 
+		[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
+		[BurstDiscard]
+		public void TestUseOnlySetAllowReadAndWriteAccess(
+			bool allowReadOrWriteAccess)
+		{
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+			AtomicSafetyHandle.SetAllowReadOrWriteAccess(
+				m_Safety,
+				allowReadOrWriteAccess);
+#endif
+		}
+
+		/// 
+		/// Throw an exception if the object isn't readable
+		/// 
+		[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
+		private void RequireReadAccess()
+		{
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+			AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
+#endif
+		}
+
+		/// 
+		/// Throw an exception if the object isn't writable
+		/// 
+		[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
+		private void RequireWriteAccess()
+		{
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+			AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
+#endif
+		}
+	}
+
+	/// 
+	/// Provides a debugger view of .
+	/// 
+	internal sealed class NativePerJobThreadIntPtrsDebugView
+	{
+		/// 
+		/// The object to provide a debugger view for
+		/// 
+		private NativePerJobThreadIntPtrs m_Ptrs;
+
+		/// 
+		/// Create the debugger view
+		/// 
+		/// 
+		/// 
+		/// The object to provide a debugger view for
+		/// 
+		public NativePerJobThreadIntPtrsDebugView(NativePerJobThreadIntPtrs ptrs)
+		{
+			m_Ptrs = ptrs;
+		}
+
+		/// 
+        /// Get the elements of the array as a managed array
+        /// 
+        public int[] Items
+        {
+            get
+            {
+            	int[] arr = new int[m_Ptrs.Length];
+            	for (int i = 0; i < arr.Length; i++) arr[i] = m_Ptrs[i];
+                return arr;
+            }
+        }
+	}
+}
diff --git a/JacksonDunstanNativeCollections/NativePerJobThreadIntPtrs.cs.meta b/JacksonDunstanNativeCollections/NativePerJobThreadIntPtrs.cs.meta
new file mode 100644
index 0000000..f68065a
--- /dev/null
+++ b/JacksonDunstanNativeCollections/NativePerJobThreadIntPtrs.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 87e73f6ca4f7441be9b9ea7916d081d5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: