Compare commits

...

34 Commits
v1.1.0 ... main

Author SHA1 Message Date
bcb9895dc0 Upgraded to .NET 5 2020-09-20 21:49:35 +02:00
02e6994b4f Upgraded to .NET 5 2020-09-20 21:40:53 +02:00
a75596d64b Update 2020-09-20 21:37:00 +02:00
ae6aece08b Upgraded to .NET 5 2020-09-20 21:36:50 +02:00
6578d9a08e Version 1.4.0 2020-08-22 20:30:22 +02:00
b1fba18f7f Added text about store and load 2020-08-22 20:29:37 +02:00
4f038c81c5 Added test case for store and load 2020-08-22 20:26:47 +02:00
c4abad94cc Added documentation 2020-08-22 20:20:07 +02:00
b72059dfa1 Implemented store and load methods 2020-08-04 20:51:34 +02:00
0d6c719148 Implemented store and load methods 2020-08-04 20:51:13 +02:00
42b7d4349d Optimized variable 2020-08-04 20:19:35 +02:00
b124716f49 Updated 2020-08-04 19:20:57 +02:00
96b1fbf2b6 Version 1.3.0 2020-08-04 19:17:01 +02:00
75aed77272 Added tests for 2d array 2020-08-04 19:16:07 +02:00
5fc0c90b11 Fixed corner case 2020-08-04 19:15:54 +02:00
7e00a48e67 Removed unused class 2020-08-04 17:10:34 +02:00
ed9143f240 WIP: Added first 2d implementation 2020-08-03 20:06:19 +02:00
67c29abe79 Version 1.2.0 2020-08-01 20:35:23 +02:00
0968edab80 Added possibilities to use IEnumerable<T> and ICollection<T> to create an array.
Closes:
thorsten/ExaArray#1
thorsten/ExaArray#3
thorsten/ExaArray#4
2020-08-01 20:31:11 +02:00
d2ae4895c4 Added index operator for cloning a range
Closing thorsten/ExaArray#5
2020-08-01 15:48:07 +02:00
a791ad6b0a Spelling 2020-08-01 15:47:03 +02:00
de75d12140 Added extension methods 2020-08-01 15:42:18 +02:00
c5b26afad0 Added test case for corner case 2020-08-01 15:34:23 +02:00
1c30d49965 Finished create with range implementation
thorsten/ExaArray#5 (comment)
2020-08-01 15:30:06 +02:00
4dcfa391d2 Fixed test case 2020-08-01 15:29:18 +02:00
c7c77c90fb WIP: Range creation 2020-08-01 14:06:01 +02:00
242fead373 Added test cases for range creation 2020-08-01 14:05:46 +02:00
7f56894efe Added possibility to clone an existing ExaArray.
Closes thorsten/ExaArray#2 (comment)
2020-08-01 10:44:31 +02:00
8c0722be44 Added tests for invalid indices 2020-08-01 09:55:01 +02:00
3f76c61d94 Added check for invalid index 2020-08-01 09:54:31 +02:00
79eb5fc9c9 Added missed tests for extending 2020-08-01 09:34:40 +02:00
3c49516f23 Defined categories 2020-08-01 09:18:19 +02:00
de0da97e95 Changed project url to Github 2020-07-30 20:07:40 +02:00
62aa37d31f Fixed optimization. Thanks @jkotas.
https://github.com/dotnet/runtime/issues/12221#issuecomment-665815891
2020-07-30 19:53:01 +02:00
11 changed files with 1521 additions and 153 deletions

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@ -1,18 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>Exa</RootNamespace>
<PackageId>ExaArray</PackageId>
<Authors>Thorsten Sommer</Authors>
<Company>Thorsten Sommer</Company>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageProjectUrl>https://code.tsommer.org/thorsten/ExaArray</PackageProjectUrl>
<PackageProjectUrl>https://github.com/SommerEngineering/ExaArray</PackageProjectUrl>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
<RepositoryUrl>https://code.tsommer.org/thorsten/ExaArray</RepositoryUrl>
<PackageVersion>1.1.0</PackageVersion>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
<PackageVersion>1.4.1-rc</PackageVersion>
<AssemblyVersion>1.4.1</AssemblyVersion>
<FileVersion>1.4.1</FileVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@ -0,0 +1,265 @@
using System;
using System.Collections.Generic;
namespace Exa
{
public partial class ExaArray1D<T>
{
/// <summary>
/// Creates a new ExaArray1D from another.
/// </summary>
/// <remarks>
/// When <c>T</c> is a value type, data gets copied as values. When <c>T</c> is a reference type, the pointers
/// to the original objects are copied. Thus, this factory method does not create a deep copy.
///
/// Performance: O(n)
/// </remarks>
/// <param name="other">The instance from which the new instance is to be created.</param>
/// <returns>The new instance</returns>
public static ExaArray1D<T> CreateFrom(ExaArray1D<T> other)
{
var next = new ExaArray1D<T>(other.OptimizationStrategy)
{
Length = other.Length,
chunks = new T[other.chunks.Length][]
};
for (var n = 0; n < other.chunks.Length; n++)
{
next.chunks[n] = new T[other.chunks[n].Length];
Array.Copy(other.chunks[n], next.chunks[n], other.chunks[n].Length);
}
return next;
}
/// <summary>
/// Creates a new ExaArray1D from another, respecting the given range.
/// </summary>
/// <remarks>
/// When <c>T</c> is a value type, data gets copied as values. When <c>T</c> is a reference type, the pointers
/// to the original objects are copied. Thus, this factory method does not create a deep copy.
///
/// Performance: O(n)
///
/// The indices are inclusive.
/// </remarks>
/// <param name="other">The instance from which the new instance is to be created.</param>
/// <param name="indexFrom">The first source element which should be part of the new array.</param>
/// <param name="indexTo">The last source element which should be part of the new array.</param>
/// <returns>The new instance</returns>
/// <exception cref="IndexOutOfRangeException">Throws, when one or both of the indices are out of range.</exception>
public static ExaArray1D<T> CreateFrom(ExaArray1D<T> other, ulong indexFrom, ulong indexTo)
{
if (indexTo < indexFrom)
throw new IndexOutOfRangeException("Index to must be greater than index from.");
if (indexTo >= other.Length || indexFrom >= other.Length)
throw new IndexOutOfRangeException("Index to must be within the range of the source.");
//
// Determine the source start chunk and index.
//
int sourceChunkIndexTo = -1;
int sourceElementIndexTo = -1;
switch (other.OptimizationStrategy)
{
case Strategy.MAX_PERFORMANCE:
sourceChunkIndexTo = (int) (indexTo / MAX_CAPACITY_ARRAY_PERFORMANCE);
sourceElementIndexTo = (int) (indexTo - (ulong) sourceChunkIndexTo * MAX_CAPACITY_ARRAY_PERFORMANCE);
break;
case Strategy.MAX_ELEMENTS:
sourceChunkIndexTo = (int) (indexTo / MAX_CAPACITY_ARRAY);
sourceElementIndexTo = (int) (indexTo - (ulong) sourceChunkIndexTo * MAX_CAPACITY_ARRAY);
break;
}
//
// Determine the source end chunk and index.
//
int sourceChunkIndexFrom = -1;
int sourceElementIndexFrom = -1;
switch (other.OptimizationStrategy)
{
case Strategy.MAX_PERFORMANCE:
sourceChunkIndexFrom = (int) (indexFrom / MAX_CAPACITY_ARRAY_PERFORMANCE);
sourceElementIndexFrom = (int) (indexFrom - (ulong) sourceChunkIndexFrom * MAX_CAPACITY_ARRAY_PERFORMANCE);
break;
case Strategy.MAX_ELEMENTS:
sourceChunkIndexFrom = (int) (indexFrom / MAX_CAPACITY_ARRAY);
sourceElementIndexFrom = (int) (indexFrom - (ulong) sourceChunkIndexFrom * MAX_CAPACITY_ARRAY);
break;
}
// How many element we have to copy?
ulong newRange = indexTo - indexFrom + 1;
//
// Determine, how many total chunks we need for the copy.
//
int destChunks = -1;
switch (other.OptimizationStrategy)
{
case Strategy.MAX_PERFORMANCE:
destChunks = (int) ((newRange - 1) / MAX_CAPACITY_ARRAY_PERFORMANCE) + 1;
break;
case Strategy.MAX_ELEMENTS:
destChunks = (int) ((newRange - 1) / MAX_CAPACITY_ARRAY) + 1;
break;
}
// Create the copy and allocate the needed number of outer chunk.
var next = new ExaArray1D<T>(other.OptimizationStrategy)
{
Length = newRange,
chunks = new T[destChunks][],
};
//
// Variables for the copy process.
//
int sourceChunkIndex = sourceChunkIndexFrom;
int destinationChunkIndex = 0;
int sourceElementIndex = sourceElementIndexFrom;
int destinationElementIndex = 0;
ulong leftOverTotal = newRange;
do
{
//
// Determine how many elements we copy next.
//
uint numberToCopy = 0;
// Case: small number of elements from first chunk only
if (sourceChunkIndexFrom == sourceChunkIndexTo)
numberToCopy = (uint) (sourceElementIndexTo - sourceElementIndexFrom + 1);
// Case: this is first chunk of multiple chunks + start somewhere in the middle
else if (sourceElementIndex > 0)
numberToCopy = (uint) (other.maxArrayCapacity - sourceElementIndex);
// Case: multiple chunks + we are in the middle of huge copy process + next chunk does __not__ exist
else if (next.chunks[destinationChunkIndex] == null && sourceElementIndex == 0 && leftOverTotal >= other.maxArrayCapacity)
numberToCopy = other.maxArrayCapacity;
// Case: multiple chunks + we are in the middle of huge copy process + next chunk does exist
else if (next.chunks[destinationChunkIndex] != null && sourceElementIndex == 0 && leftOverTotal >= other.maxArrayCapacity)
numberToCopy = (uint) (other.maxArrayCapacity - next.chunks[destinationChunkIndex].Length);
// Case: multiple chunks + this seems to be the last chunk
else if (sourceElementIndex == 0 && leftOverTotal < other.maxArrayCapacity)
numberToCopy = (uint) leftOverTotal;
//
// Can we allocate an entire chunk or do we have to fill up the existing
// chunk first?
//
if(next.chunks[destinationChunkIndex] == null)
next.chunks[destinationChunkIndex] = new T[numberToCopy];
else
{
var extended = new T[next.chunks[destinationChunkIndex].Length + numberToCopy];
Array.Copy(next.chunks[destinationChunkIndex], extended, next.chunks[destinationChunkIndex].Length);
next.chunks[destinationChunkIndex] = extended;
}
// Copy the data:
Array.Copy(other.chunks[sourceChunkIndex], sourceElementIndex, next.chunks[destinationChunkIndex], destinationElementIndex, numberToCopy);
//
// Update the state machine.
//
var needNewDestinationChunk = next.chunks[destinationChunkIndex].Length == next.maxArrayCapacity;
var readNextSourceChunk = sourceElementIndex + numberToCopy == other.maxArrayCapacity;
var leftOverCurrentSourceChunk = (int) (readNextSourceChunk ? 0 : other.maxArrayCapacity - sourceElementIndex - numberToCopy);
leftOverTotal -= numberToCopy;
sourceChunkIndex = readNextSourceChunk ? sourceChunkIndex + 1 : sourceChunkIndex;
destinationChunkIndex = needNewDestinationChunk ? destinationChunkIndex + 1 : destinationChunkIndex;
sourceElementIndex = leftOverCurrentSourceChunk;
destinationElementIndex = (int) (needNewDestinationChunk ? 0 : numberToCopy);
} while (leftOverTotal > 0);
return next;
}
/// <summary>
/// Creates a new ExaArray1D from an enumerable sequence of items. The number of items in the sequence is __unknown__.
/// </summary>
/// <remarks>
/// This factory method is slow because the number of items in the sequence is unknown. When you know the
/// number of items, you should use another factory method, where the number of items can be provided.
///
/// Performance: O(n)
/// </remarks>
/// <param name="sequence">The sequence to consume in order to create the array.</param>
/// <param name="strategy">The optional optimization strategy.</param>
/// <returns>The desired instance</returns>
public static ExaArray1D<T> CreateFrom(IEnumerable<T> sequence, Strategy strategy = Strategy.MAX_PERFORMANCE)
{
var inst = new ExaArray1D<T>(strategy);
ulong position = 0;
foreach (var element in sequence)
{
inst.Extend();
inst[position++] = element;
}
return inst;
}
/// <summary>
/// Creates a new ExaArray1D from an enumerable sequence of items. The number of items in the sequence is __known__.
/// </summary>
/// <remarks>
/// Creates an array with <c>length</c> items. When the sequence contains less elements, the remaining values are <c>default(T)</c>.
/// When the sequence contains more elements, these additional elements getting ignored.
///
/// Performance: O(n)
/// </remarks>
/// <param name="sequence">The sequence to consume in order to create the array.</param>
/// <param name="length">The number of elements in the sequence. When the sequence contains more elements, these additional elements are ignored.</param>
/// <param name="strategy">The optional optimization strategy.</param>
/// <returns>The desired instance</returns>
public static ExaArray1D<T> CreateFrom(IEnumerable<T> sequence, ulong length, Strategy strategy = Strategy.MAX_PERFORMANCE)
{
var inst = new ExaArray1D<T>(strategy);
inst.Extend(length);
ulong position = 0;
foreach (var element in sequence)
{
if(position == length)
break;
inst[position++] = element;
}
return inst;
}
/// <summary>
/// Creates a new ExaArray1D from a collection of items.
/// </summary>
/// <remarks>
/// Performance: O(n)
/// </remarks>
/// <param name="collection">The collection to use</param>
/// <param name="strategy">The optional optimization strategy.</param>
/// <returns>The desired instance</returns>
public static ExaArray1D<T> CreateFrom(ICollection<T> collection, Strategy strategy = Strategy.MAX_PERFORMANCE)
{
var inst = new ExaArray1D<T>(strategy);
inst.Extend((ulong) collection.Count);
ulong position = 0;
foreach (var element in collection)
inst[position++] = element;
return inst;
}
}
}

View File

@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace Exa
{
@ -8,7 +11,8 @@ namespace Exa
/// to 4.6 quintillion (4,607,183,514,018,780,000) or 4.6 exa elements.
/// </summary>
/// <typeparam name="T">The desired type to use, e.g. byte, int, etc.</typeparam>
public sealed class ExaArray1D<T>
[Serializable]
public sealed partial class ExaArray1D<T> : ISerializable
{
/// <summary>
/// Unfortunately, this seems to be the maximal number of entries an
@ -33,9 +37,6 @@ namespace Exa
/// </summary>
public const ulong MAX_NUMBER_ELEMENTS_PERFORMANCE = 1_152_921_504_606_850_000;
private readonly ulong maxCapacityArray;
private readonly ulong maxNumberElements;
/// <summary>
/// Gets the configured optimization strategy.
/// </summary>
@ -44,24 +45,21 @@ namespace Exa
// Chunk storage:
private T[][] chunks = new T[1][];
// Max. array and chunk capacity:
private readonly uint maxArrayCapacity;
// Max. number elements:
private readonly ulong maxElements;
/// <summary>
/// Creates an empty one-dimensional exa-scale array.
/// </summary>
public ExaArray1D(Strategy strategy = Strategy.MAX_PERFORMANCE)
{
this.OptimizationStrategy = strategy;
this.chunks[0] = new T[0];
if (this.OptimizationStrategy == Strategy.MAX_PERFORMANCE)
{
this.maxCapacityArray = MAX_CAPACITY_ARRAY_PERFORMANCE;
this.maxNumberElements = MAX_NUMBER_ELEMENTS_PERFORMANCE;
}
else
{
this.maxCapacityArray = MAX_CAPACITY_ARRAY;
this.maxNumberElements = MAX_NUMBER_ELEMENTS;
}
this.OptimizationStrategy = strategy;
this.maxElements = this.OptimizationStrategy == Strategy.MAX_PERFORMANCE ? MAX_NUMBER_ELEMENTS_PERFORMANCE : MAX_NUMBER_ELEMENTS;
this.maxArrayCapacity = this.OptimizationStrategy == Strategy.MAX_PERFORMANCE ? MAX_CAPACITY_ARRAY_PERFORMANCE : MAX_CAPACITY_ARRAY;
}
/// <summary>
@ -81,15 +79,15 @@ namespace Exa
/// <c>extendBy</c> argument exceeds the limit of 4,607,183,514,018,780,000 elements.</exception>
public void Extend(ulong extendBy = 1)
{
if(extendBy > this.maxNumberElements || this.Length + extendBy >= this.maxNumberElements)
throw new ArgumentOutOfRangeException($"It is not possible to extend more than {this.maxNumberElements} elements.");
if (extendBy > this.maxElements || this.Length + extendBy >= this.maxElements)
throw new ArgumentOutOfRangeException($"It is not possible to extend more than {this.maxElements} elements.");
this.Length += extendBy;
var availableInCurrentChunk = this.maxCapacityArray - (ulong) (this.chunks[^1]?.Length ?? 0);
var availableInCurrentChunk = this.maxArrayCapacity - (ulong) (this.chunks[^1]?.Length ?? 0);
if (extendBy >= (ulong)availableInCurrentChunk)
{
// Extend the current chunk to its max:
var extendedInner = new T[this.maxCapacityArray];
var extendedInner = new T[this.maxArrayCapacity];
Array.Copy(this.chunks[^1], extendedInner, this.chunks[^1].Length);
this.chunks[^1] = extendedInner;
@ -102,7 +100,7 @@ namespace Exa
do
{
ulong allocating = leftOver >= this.maxCapacityArray ? this.maxCapacityArray : leftOver;
ulong allocating = leftOver >= this.maxArrayCapacity ? this.maxArrayCapacity : leftOver;
leftOver -= allocating;
// First, we allocate space for the new chunk:
@ -145,21 +143,48 @@ namespace Exa
{
get
{
if(index >= this.maxNumberElements)
if(index >= this.maxElements)
throw new IndexOutOfRangeException();
int chunkIndex = -1;
int elementIndex = -1;
switch (this.OptimizationStrategy)
{
case Strategy.MAX_PERFORMANCE:
chunkIndex = (int) (index / MAX_CAPACITY_ARRAY_PERFORMANCE);
elementIndex = (int) (index - (ulong) chunkIndex * MAX_CAPACITY_ARRAY_PERFORMANCE);
break;
case Strategy.MAX_ELEMENTS:
chunkIndex = (int) (index / MAX_CAPACITY_ARRAY);
elementIndex = (int) (index - (ulong) chunkIndex * MAX_CAPACITY_ARRAY);
break;
}
if (chunkIndex >= this.chunks.Length || elementIndex >= this.chunks[chunkIndex].Length)
throw new IndexOutOfRangeException();
int chunkIndex = (int) (index / this.maxCapacityArray);
int elementIndex = (int) (index - (ulong) chunkIndex * this.maxCapacityArray);
return this.chunks[chunkIndex][elementIndex];
}
set
{
if(index >= this.maxNumberElements)
if(index >= this.maxElements)
throw new IndexOutOfRangeException();
int chunkIndex = (int) (index / this.maxCapacityArray);
int elementIndex = (int) (index - (ulong) chunkIndex * this.maxCapacityArray);
int chunkIndex = -1;
int elementIndex = -1;
switch (this.OptimizationStrategy)
{
case Strategy.MAX_PERFORMANCE:
chunkIndex = (int) (index / MAX_CAPACITY_ARRAY_PERFORMANCE);
elementIndex = (int) (index - (ulong) chunkIndex * MAX_CAPACITY_ARRAY_PERFORMANCE);
break;
case Strategy.MAX_ELEMENTS:
chunkIndex = (int) (index / MAX_CAPACITY_ARRAY);
elementIndex = (int) (index - (ulong) chunkIndex * MAX_CAPACITY_ARRAY);
break;
}
if (chunkIndex >= this.chunks.Length || elementIndex >= this.chunks[chunkIndex].Length)
throw new IndexOutOfRangeException();
@ -167,6 +192,23 @@ namespace Exa
}
}
/// <summary>
/// Creates a new ExaArray1D from this instance, respecting the given range.
/// </summary>
/// <remarks>
/// When <c>T</c> is a value type, data gets copied as values. When <c>T</c> is a reference type, the pointers
/// to the original objects are copied. Thus, this factory method does not create a deep copy.
///
/// Performance: O(n)
///
/// The indices are inclusive.
/// </remarks>
/// <param name="indexFrom">The first source element which should be part of the new array.</param>
/// <param name="indexTo">The last source element which should be part of the new array.</param>
/// <returns>The new instance</returns>
/// <exception cref="IndexOutOfRangeException">Throws, when one or both of the indices are out of range.</exception>
public ExaArray1D<T> this[ulong indexFrom, ulong indexTo] => ExaArray1D<T>.CreateFrom(this, indexFrom, indexTo);
/// <summary>
/// Yields an enumerator across all elements.
/// </summary>
@ -180,5 +222,72 @@ namespace Exa
for (ulong n = 0; n < this.Length; n++)
yield return this[n];
}
#region Store and load
/// <summary>
/// Stores the exa array into a stream. <b>Please read the remarks regarding security issues.</b>
/// </summary>
/// <remarks>
/// The data stored in this way should never be part of a public API. Serializing and
/// deserializing is not secure: an attacker can manipulate the data in a targeted
/// manner to compromise the API server, etc.
///
/// This method does not dispose the stream.
/// </remarks>
public void Store(Stream outputStream)
{
var formatter = new BinaryFormatter();
formatter.Serialize(outputStream, this);
}
/// <summary>
/// Restores an exa array from the given stream. <b>Please read the remarks regarding security issues.</b>
/// </summary>
/// <remarks>
/// The data loaded in this way should never be part of a public API. Serializing and
/// deserializing is not secure: an attacker can manipulate the data in a targeted
/// manner to compromise the API server, etc.
///
/// This method does not dispose the stream.
/// </remarks>
public static ExaArray1D<T> Restore(Stream inputStream)
{
var formatter = new BinaryFormatter();
return formatter.Deserialize(inputStream) as ExaArray1D<T>;
}
#endregion
#region Serialization
/// <summary>
/// This method serves for the serialization process. Do not call it manually.
/// </summary>
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("version", "v1");
info.AddValue("strategy", this.OptimizationStrategy, typeof(Strategy));
info.AddValue("length", this.Length);
info.AddValue("chunks", this.chunks, typeof(T[][]));
}
private ExaArray1D(SerializationInfo info, StreamingContext context)
{
switch (info.GetString("version"))
{
case "v1":
this.Length = info.GetUInt64("length");
this.chunks = info.GetValue("chunks", typeof(T[][])) as T[][];
this.OptimizationStrategy = (Strategy) info.GetValue("strategy", typeof(Strategy));
break;
}
this.chunks[0] ??= new T[0];
this.maxElements = this.OptimizationStrategy == Strategy.MAX_PERFORMANCE ? MAX_NUMBER_ELEMENTS_PERFORMANCE : MAX_NUMBER_ELEMENTS;
this.maxArrayCapacity = this.OptimizationStrategy == Strategy.MAX_PERFORMANCE ? MAX_CAPACITY_ARRAY_PERFORMANCE : MAX_CAPACITY_ARRAY;
}
#endregion
}
}

151
ExaArray/ExaArray2D.cs Normal file
View File

@ -0,0 +1,151 @@
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace Exa
{
/// <summary>
/// The two-dimensional exa-scale array. Can grow up to 18,446,744,073,709,551,615 elements in total.
/// </summary>
[Serializable]
public sealed class ExaArray2D<T> : ISerializable
{
/// <summary>
/// The total number of possible elements.
/// </summary>
public const ulong MAX_NUMBER_ELEMENTS = ulong.MaxValue;
private ulong sumLengthOrdinates = 0;
// Chunk storage:
private readonly ExaArray1D<ExaArray1D<T>> chunks = new ExaArray1D<ExaArray1D<T>>(Strategy.MAX_PERFORMANCE);
/// <summary>
/// Constructs a two-dimensional exa-scale array.
/// </summary>
public ExaArray2D()
{
}
/// <summary>
/// Returns the current total number of elements across all dimensions.
/// </summary>
/// <remarks>
/// Performance: O(1)
/// </remarks>
public ulong Length => this.sumLengthOrdinates;
/// <summary>
/// Gets or sets an element of the array.
/// </summary>
/// <remarks>
/// Getting a value: When asking for elements which are not yet allocated, returns <c>default(T)</c>. Performance: O(1).
///
/// Setting a value: The underlying data structure gets extended on demand as necessary. The array can and will grow
/// on demand per index. For example, the abscissa index 5 might have allocated memory for 15 elements while abscissa
/// index 16 have allocated memory for 1,000 elements.
///
/// Performance, when the memory is already allocated: O(1)
/// Performance to extend on demand: O(n)
///
/// On the abscissa, you extend up to 1,152,921,504,606,850,000 entries. Across all dimensions, you can have 18,446,744,073,709,551,615
/// elements on total.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">Throws, when you tried to extend more than 1,152,921,504,606,850,000 elements on the abscissa
/// or you tried to extend to more than 18,446,744,073,709,551,615 elements in total.</exception>
public T this[ulong indexAbscissa, ulong indexOrdinate]
{
get
{
if (this.chunks.Length == 0 || indexAbscissa >= this.chunks.Length || indexOrdinate >= this.chunks[indexAbscissa]?.Length)
return default(T);
return this.chunks[indexAbscissa][indexOrdinate];
}
set
{
if(this.chunks.Length == 0 || indexAbscissa >= this.chunks.Length)
this.chunks.Extend(indexAbscissa - this.chunks.Length + 1);
this.chunks[indexAbscissa] ??= new ExaArray1D<T>(Strategy.MAX_PERFORMANCE);
if(this.chunks[indexAbscissa].Length == 0 || indexOrdinate >= this.chunks[indexAbscissa].Length - 1)
{
var extendBy = (indexOrdinate - this.chunks[indexAbscissa].Length) + 1;
if(extendBy > MAX_NUMBER_ELEMENTS - this.sumLengthOrdinates)
throw new ArgumentOutOfRangeException($"It is not possible to extend more than {MAX_NUMBER_ELEMENTS} total elements across all dimensions.");
this.chunks[indexAbscissa].Extend(extendBy);
this.sumLengthOrdinates += extendBy;
}
this.chunks[indexAbscissa][indexOrdinate] = value;
}
}
#region Store and load
/// <summary>
/// Stores the exa array into a stream. <b>Please read the remarks regarding security issues.</b>
/// </summary>
/// <remarks>
/// The data stored in this way should never be part of a public API. Serializing and
/// deserializing is not secure: an attacker can manipulate the data in a targeted
/// manner to compromise the API server, etc.
///
/// This method does not dispose the stream.
/// </remarks>
public void Store(Stream outputStream)
{
var formatter = new BinaryFormatter();
formatter.Serialize(outputStream, this);
}
/// <summary>
/// Restores an exa array from the given stream. <b>Please read the remarks regarding security issues.</b>
/// </summary>
/// <remarks>
/// The data loaded in this way should never be part of a public API. Serializing and
/// deserializing is not secure: an attacker can manipulate the data in a targeted
/// manner to compromise the API server, etc.
///
/// This method does not dispose the stream.
/// </remarks>
public static ExaArray2D<T> Restore(Stream inputStream)
{
var formatter = new BinaryFormatter();
return formatter.Deserialize(inputStream) as ExaArray2D<T>;
}
#endregion
#region Serialization
/// <summary>
/// This method serves for the serialization process. Do not call it manually.
/// </summary>
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("version", "v1");
info.AddValue("length", this.Length);
info.AddValue("chunks", this.chunks, typeof(ExaArray1D<ExaArray1D<T>>));
}
private ExaArray2D(SerializationInfo info, StreamingContext context)
{
switch (info.GetString("version"))
{
case "v1":
this.sumLengthOrdinates = info.GetUInt64("length");
this.chunks = info.GetValue("chunks", typeof(ExaArray1D<ExaArray1D<T>>)) as ExaArray1D<ExaArray1D<T>>;
break;
default:
break;
}
}
#endregion
}
}

View File

@ -0,0 +1,41 @@
using System;
namespace Exa
{
/// <summary>
/// Extension methods for <c>ExaArray1D</c>.
/// </summary>
public static class ExtensionsExaArray1D
{
/// <summary>
/// Creates a new ExaArray1D from this instance.
/// </summary>
/// <remarks>
/// When <c>T</c> is a value type, data gets copied as values. When <c>T</c> is a reference type, the pointers
/// to the original objects are copied. Thus, this factory method does not create a deep copy.
///
/// Performance: O(n)
/// </remarks>
/// <param name="other">The instance from which the new instance is to be created.</param>
/// <returns>The new instance</returns>
public static ExaArray1D<T> Clone<T>(this ExaArray1D<T> other) => ExaArray1D<T>.CreateFrom(other);
/// <summary>
/// Creates a new ExaArray1D from this instance, respecting the given range.
/// </summary>
/// <remarks>
/// When <c>T</c> is a value type, data gets copied as values. When <c>T</c> is a reference type, the pointers
/// to the original objects are copied. Thus, this factory method does not create a deep copy.
///
/// Performance: O(n)
///
/// The indices are inclusive.
/// </remarks>
/// <param name="other">The instance from which the new instance is to be created.</param>
/// <param name="indexFrom">The first source element which should be part of the new array.</param>
/// <param name="indexTo">The last source element which should be part of the new array.</param>
/// <returns>The new instance</returns>
/// <exception cref="IndexOutOfRangeException">Throws, when one or both of the indices are out of range.</exception>
public static ExaArray1D<T> Clone<T>(this ExaArray1D<T> other, ulong indexFrom, ulong indexTo) => ExaArray1D<T>.CreateFrom(other, indexFrom, indexTo);
}
}

View File

@ -0,0 +1,50 @@
using System.Collections.Generic;
namespace Exa
{
/// <summary>
/// Contains extension methods.
/// </summary>
public static class ExtensionsFramework
{
/// <summary>
/// Creates a new ExaArray1D from this collection of items.
/// </summary>
/// <remarks>
/// Performance: O(n)
/// </remarks>
/// <param name="collection">The collection to use</param>
/// <param name="strategy">The optional optimization strategy.</param>
/// <returns>The desired instance</returns>
public static ExaArray1D<T> AsExaArray<T>(this ICollection<T> collection, Strategy strategy = Strategy.MAX_PERFORMANCE) => ExaArray1D<T>.CreateFrom(collection, strategy);
/// <summary>
/// Creates a new ExaArray1D from this enumerable sequence of items. The number of items in the sequence is __known__.
/// </summary>
/// <remarks>
/// Creates an array with <c>length</c> items. When this sequence contains less elements, the remaining values are <c>default(T)</c>.
/// When the sequence contains more elements, these additional elements getting ignored.
///
/// Performance: O(n)
/// </remarks>
/// <param name="sequence">The sequence to consume in order to create the array.</param>
/// <param name="length">The number of elements in the sequence. When the sequence contains more elements, these additional elements are ignored.</param>
/// <param name="strategy">The optional optimization strategy.</param>
/// <returns>The desired instance</returns>
public static ExaArray1D<T> AsExaArray<T>(this IEnumerable<T> sequence, ulong length, Strategy strategy = Strategy.MAX_PERFORMANCE) => ExaArray1D<T>.CreateFrom(sequence, length, strategy);
/// <summary>
/// Creates a new ExaArray1D from this enumerable sequence of items. The number of items in the sequence is __unknown__.
/// </summary>
/// <remarks>
/// This method is slow because the number of items in this sequence is unknown. When you know the
/// number of items, you should use another factory method, where the number of items can be provided.
///
/// Performance: O(n)
/// </remarks>
/// <param name="sequence">The sequence to consume in order to create the array.</param>
/// <param name="strategy">The optional optimization strategy.</param>
/// <returns>The desired instance</returns>
public static ExaArray1D<T> AsExaArray<T>(this IEnumerable<T> sequence, Strategy strategy = Strategy.MAX_PERFORMANCE) => ExaArray1D<T>.CreateFrom(sequence, strategy);
}
}

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Exa;
@ -8,12 +10,18 @@ using NUnit.Framework;
namespace ExaArrayTests
{
[ExcludeFromCodeCoverage]
public class ExaArray1DTests
{
[ExcludeFromCodeCoverage]
public class InfinityArrayTests
[Serializable]
private class TestClass
{
public int Age { get; set; }
}
[Test]
[Category("cover")]
[Category("normal")]
public void CreatingNormalSize01()
{
var exaA = new ExaArray1D<byte>();
@ -36,6 +44,8 @@ namespace ExaArrayTests
}
[Test]
[Category("cover")]
[Category("normal")]
public void CreatingNormalSize02()
{
var exaA = new ExaArray1D<int>();
@ -54,6 +64,129 @@ namespace ExaArrayTests
}
[Test]
[Category("normal")]
[Category("cover")]
public void ExtendingTooFar01()
{
var exaA = new ExaArray1D<byte>();
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
// Cannot handle more than 1.1 quintillion elements:
exaA.Extend(ulong.MaxValue);
});
}
[Test]
[Category("normal")]
[Category("cover")]
public void ExtendingToEndFirstChunk01()
{
const uint MAX = 1_073_741_824;
var exaA = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exaA.Extend(MAX - 2);
Assert.That(exaA.Length, Is.EqualTo(MAX - 2));
exaA.Extend(2);
Assert.That(exaA.Length, Is.EqualTo(MAX));
}
[Test]
[Category("normal")]
[Category("cover")]
public void GetInvalidIndex01()
{
var exaPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exaPerf.Extend(2);
Assert.Throws<IndexOutOfRangeException>(() =>
{
var t = exaPerf[ulong.MaxValue]; // Because index >= max
});
Assert.DoesNotThrow(() =>
{
var t1 = exaPerf[0];
var t2 = exaPerf[1];
});
Assert.Throws<IndexOutOfRangeException>(() =>
{
var t = exaPerf[2]; // Because we don't allocate
});
exaPerf = null;
var exaElem = new ExaArray1D<byte>(Strategy.MAX_ELEMENTS);
exaElem.Extend(2);
Assert.Throws<IndexOutOfRangeException>(() =>
{
var t = exaElem[ulong.MaxValue]; // Because index >= max
});
Assert.DoesNotThrow(() =>
{
var t1 = exaElem[0];
var t2 = exaElem[1];
});
Assert.Throws<IndexOutOfRangeException>(() =>
{
var t = exaElem[2]; // Because we don't allocate
});
}
[Test]
[Category("normal")]
[Category("cover")]
public void SetInvalidIndex01()
{
var exaPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exaPerf.Extend(2);
Assert.Throws<IndexOutOfRangeException>(() =>
{
exaPerf[ulong.MaxValue] = 0x00; // Because index >= max
});
Assert.DoesNotThrow(() =>
{
exaPerf[0] = 0x01;
exaPerf[1] = 0x02;
});
Assert.Throws<IndexOutOfRangeException>(() =>
{
var t = exaPerf[2] = 0x03; // Because we don't allocate
});
exaPerf = null;
var exaElem = new ExaArray1D<byte>(Strategy.MAX_ELEMENTS);
exaElem.Extend(2);
Assert.Throws<IndexOutOfRangeException>(() =>
{
exaElem[ulong.MaxValue] = 0x00; // Because index >= max
});
Assert.DoesNotThrow(() =>
{
exaElem[0] = 0x01;
exaElem[1] = 0x02;
});
Assert.Throws<IndexOutOfRangeException>(() =>
{
exaElem[2] = 0x03; // Because we don't allocate
});
}
[Test]
[Category("intensive")]
public void CountingHugeSize01()
{
var exaA = new ExaArray1D<byte>();
@ -68,6 +201,457 @@ namespace ExaArrayTests
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFrom001()
{
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(2);
exPerf[0] = 0x01;
exPerf[1] = 0x02;
var next = ExaArray1D<byte>.CreateFrom(exPerf);
Assert.That(next.Length, Is.EqualTo(exPerf.Length));
Assert.That(next.OptimizationStrategy, Is.EqualTo(exPerf.OptimizationStrategy));
Assert.That(next.Items(), Is.EquivalentTo(exPerf.Items()));
exPerf = null;
next = null;
var exElem = new ExaArray1D<byte>(Strategy.MAX_ELEMENTS);
exElem.Extend(2);
exElem[0] = 0x03;
exElem[1] = 0x04;
next = ExaArray1D<byte>.CreateFrom(exElem);
Assert.That(next.Length, Is.EqualTo(exElem.Length));
Assert.That(next.OptimizationStrategy, Is.EqualTo(exElem.OptimizationStrategy));
Assert.That(next.Items(), Is.EquivalentTo(exElem.Items()));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFrom002Objects()
{
var exPerf = new ExaArray1D<object>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(2);
exPerf[0] = new object();
exPerf[1] = new object();
var next = ExaArray1D<object>.CreateFrom(exPerf);
Assert.That(next.Length, Is.EqualTo(exPerf.Length));
Assert.That(next.OptimizationStrategy, Is.EqualTo(exPerf.OptimizationStrategy));
Assert.That(next[0], Is.SameAs(exPerf[0]));
Assert.That(next[1], Is.SameAs(exPerf[1]));
Assert.That(next[0], Is.Not.SameAs(next[1]));
exPerf = null;
next = null;
var exElem = new ExaArray1D<object>(Strategy.MAX_ELEMENTS);
exElem.Extend(2);
exElem[0] = new object();
exElem[1] = new object();
next = ExaArray1D<object>.CreateFrom(exElem);
Assert.That(next.Length, Is.EqualTo(exElem.Length));
Assert.That(next.OptimizationStrategy, Is.EqualTo(exElem.OptimizationStrategy));
Assert.That(next[0], Is.SameAs(exElem[0]));
Assert.That(next[1], Is.SameAs(exElem[1]));
Assert.That(next[0], Is.Not.SameAs(next[1]));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFrom003()
{
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(5_000_000_000); // more than one chunk
var next = ExaArray1D<byte>.CreateFrom(exPerf);
Assert.That(next.Length, Is.EqualTo(exPerf.Length));
Assert.DoesNotThrow(() => { next[4_999_999_999] = 0xab; });
exPerf = null;
next = null;
var exElem = new ExaArray1D<byte>(Strategy.MAX_ELEMENTS);
exElem.Extend(5_000_000_000);
next = ExaArray1D<byte>.CreateFrom(exElem);
Assert.That(next.Length, Is.EqualTo(exElem.Length));
Assert.DoesNotThrow(() => { next[4_999_999_999] = 0xab; });
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFrom004()
{
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
var next = ExaArray1D<byte>.CreateFrom(exPerf);
Assert.That(next.Length, Is.EqualTo(0));
exPerf = null;
next = null;
var exElem = new ExaArray1D<byte>(Strategy.MAX_ELEMENTS);
next = ExaArray1D<byte>.CreateFrom(exElem);
Assert.That(next.Length, Is.EqualTo(0));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFrom005Objects()
{
var exPerf = new ExaArray1D<TestClass>();
exPerf.Extend(3);
exPerf[0] = new TestClass {Age = 5};
exPerf[1] = new TestClass {Age = 10};
exPerf[2] = new TestClass {Age = 45};
var next = ExaArray1D<TestClass>.CreateFrom(exPerf);
Assert.That(next.Length, Is.EqualTo(3));
Assert.That(next[1].Age, Is.EqualTo(10));
next[1].Age = 50;
Assert.That(next[1].Age, Is.EqualTo(50));
Assert.That(exPerf[1].Age, Is.EqualTo(50));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange001()
{
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(3);
exPerf[0] = 0x01;
exPerf[1] = 0x02;
exPerf[2] = 0x03;
var next = ExaArray1D<byte>.CreateFrom(exPerf, 0, 1);
Assert.That(next.Length, Is.EqualTo(2));
Assert.That(next[0], Is.EqualTo(exPerf[0]));
Assert.That(next[1], Is.EqualTo(exPerf[1]));
Assert.That(next.OptimizationStrategy, Is.EqualTo(exPerf.OptimizationStrategy));
exPerf = null;
next = null;
var exElem = new ExaArray1D<byte>(Strategy.MAX_ELEMENTS);
exElem.Extend(3);
exElem[0] = 0x01;
exElem[1] = 0x02;
exElem[2] = 0x03;
next = ExaArray1D<byte>.CreateFrom(exElem, 1, 2);
Assert.That(next.Length, Is.EqualTo(2));
Assert.That(next[0], Is.EqualTo(exElem[1]));
Assert.That(next[1], Is.EqualTo(exElem[2]));
Assert.That(next.OptimizationStrategy, Is.EqualTo(exElem.OptimizationStrategy));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange002()
{
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(3);
exPerf[0] = 0x01;
exPerf[1] = 0x02;
exPerf[2] = 0x03;
Assert.Throws<IndexOutOfRangeException>(() =>
{
var next = ExaArray1D<byte>.CreateFrom(exPerf, 100, 200);
});
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange003()
{
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(3);
exPerf[0] = 0x01;
exPerf[1] = 0x02;
exPerf[2] = 0x03;
Assert.Throws<IndexOutOfRangeException>(() =>
{
var next = ExaArray1D<byte>.CreateFrom(exPerf, 0, 200);
});
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange004()
{
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(3);
exPerf[0] = 0x01;
exPerf[1] = 0x02;
exPerf[2] = 0x03;
Assert.Throws<IndexOutOfRangeException>(() =>
{
var next = ExaArray1D<byte>.CreateFrom(exPerf, 200, 100);
});
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange005()
{
var exPerf = new ExaArray1D<byte>(Strategy.MAX_ELEMENTS);
exPerf.Extend(3);
exPerf[0] = 0x01;
exPerf[1] = 0x02;
exPerf[2] = 0x03;
var next = ExaArray1D<byte>.CreateFrom(exPerf, 2, 2);
Assert.That(next.Length, Is.EqualTo(1));
Assert.That(next.OptimizationStrategy, Is.EqualTo(Strategy.MAX_ELEMENTS));
Assert.That(next[0], Is.EqualTo(exPerf[2]));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange006Objects()
{
var exPerf = new ExaArray1D<TestClass>();
exPerf.Extend(3);
exPerf[0] = new TestClass {Age = 5};
exPerf[1] = new TestClass {Age = 10};
exPerf[2] = new TestClass {Age = 45};
var next = ExaArray1D<TestClass>.CreateFrom(exPerf, 2, 2);
Assert.That(next.Length, Is.EqualTo(1));
Assert.That(next[0].Age, Is.EqualTo(45));
next[0].Age = 50;
Assert.That(next[0].Age, Is.EqualTo(50));
Assert.That(exPerf[2].Age, Is.EqualTo(50));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange007()
{
const uint MAX = 1_073_741_824;
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(MAX + 3); // more than one chunk
exPerf[MAX - 1 + 2] = 0x01;
exPerf[MAX - 1 + 1] = 0x02;
exPerf[MAX - 1 - 0] = 0x03;
exPerf[MAX - 1 - 1] = 0x04;
exPerf[MAX - 1 - 2] = 0x05;
exPerf[MAX - 1 - 3] = 0x06;
exPerf[MAX - 1 - 4] = 0x07;
var next = ExaArray1D<byte>.CreateFrom(exPerf, MAX - 1 - 2, MAX - 1 + 2);
Assert.That(next.Length, Is.EqualTo(5));
Assert.That(next[0], Is.EqualTo(exPerf[MAX - 1 - 2]));
Assert.That(next[1], Is.EqualTo(exPerf[MAX - 1 - 1]));
Assert.That(next[2], Is.EqualTo(exPerf[MAX - 1 - 0]));
Assert.That(next[3], Is.EqualTo(exPerf[MAX - 1 + 1]));
Assert.That(next[4], Is.EqualTo(exPerf[MAX - 1 + 2]));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange008()
{
const uint MAX = 1_073_741_824;
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(3 * MAX + 3); // more than one chunk
exPerf[MAX - 1 + 2] = 0x01;
exPerf[MAX - 1 + 1] = 0x02;
exPerf[MAX - 1 - 0] = 0x03;
exPerf[MAX - 1 - 1] = 0x04;
exPerf[MAX - 1 - 2] = 0x05;
exPerf[MAX - 1 - 3] = 0x06;
exPerf[MAX - 1 - 4] = 0x07;
var next = ExaArray1D<byte>.CreateFrom(exPerf, MAX - 1 - 2, MAX - 1 + 2);
Assert.That(next.Length, Is.EqualTo(5));
Assert.That(next[0], Is.EqualTo(exPerf[MAX - 1 - 2]));
Assert.That(next[1], Is.EqualTo(exPerf[MAX - 1 - 1]));
Assert.That(next[2], Is.EqualTo(exPerf[MAX - 1 - 0]));
Assert.That(next[3], Is.EqualTo(exPerf[MAX - 1 + 1]));
Assert.That(next[4], Is.EqualTo(exPerf[MAX - 1 + 2]));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange009()
{
const uint MAX = 1_073_741_824;
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(3 * MAX + 3); // more than one chunk
exPerf[100_000_000] = 0xFF;
exPerf[3 * MAX - 1 + 2] = 0x01;
exPerf[3 * MAX - 1 + 1] = 0x02;
exPerf[3 * MAX - 1 - 0] = 0x03;
exPerf[3 * MAX - 1 - 1] = 0x04;
exPerf[3 * MAX - 1 - 2] = 0x05;
exPerf[3 * MAX - 1 - 3] = 0x06;
exPerf[3 * MAX - 1 - 4] = 0x07;
var next = ExaArray1D<byte>.CreateFrom(exPerf, 100_000_000, 3 * MAX - 1 + 2);
Assert.That(next.Length, Is.EqualTo(exPerf.Length - 100_000_000 - 1));
Assert.That(next[0], Is.EqualTo(0xFF));
Assert.That(next[next.Length - 1 - 4], Is.EqualTo(exPerf[3 * MAX - 1 - 2]));
Assert.That(next[next.Length - 1 - 3], Is.EqualTo(exPerf[3 * MAX - 1 - 1]));
Assert.That(next[next.Length - 1 - 2], Is.EqualTo(exPerf[3 * MAX - 1 - 0]));
Assert.That(next[next.Length - 1 - 1], Is.EqualTo(exPerf[3 * MAX - 1 + 1]));
Assert.That(next[next.Length - 1 - 0], Is.EqualTo(exPerf[3 * MAX - 1 + 2]));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromRange010()
{
const uint MAX = 1_073_741_824;
var exPerf = new ExaArray1D<byte>(Strategy.MAX_PERFORMANCE);
exPerf.Extend(3 * MAX); // more than one chunk
var next1 = ExaArray1D<byte>.CreateFrom(exPerf, 0, exPerf.Length - 1);
var next2 = exPerf.Clone();
var next3 = exPerf.Clone(0, exPerf.Length - 1);
var next4 = exPerf[0, exPerf.Length - 1];
Assert.That(next1.Length, Is.EqualTo(exPerf.Length));
Assert.That(next2.Length, Is.EqualTo(exPerf.Length));
Assert.That(next3.Length, Is.EqualTo(exPerf.Length));
Assert.That(next4.Length, Is.EqualTo(exPerf.Length));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromSequence001()
{
var list = new List<int>
{
5,
7,
8,
10,
16,
};
var exPerf1 = list.AsExaArray();
Assert.That(exPerf1.OptimizationStrategy, Is.EqualTo(Strategy.MAX_PERFORMANCE));
Assert.That(exPerf1.Length, Is.EqualTo(list.Count));
Assert.That(exPerf1.Items(), Is.EquivalentTo(list));
var exPerf2 = list.AsExaArray(Strategy.MAX_ELEMENTS);
Assert.That(exPerf2.OptimizationStrategy, Is.EqualTo(Strategy.MAX_ELEMENTS));
Assert.That(exPerf2.Length, Is.EqualTo(list.Count));
Assert.That(exPerf2.Items(), Is.EquivalentTo(list));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromSequence002()
{
var array = new int[]
{
5,
7,
8,
10,
16,
};
var exPerf1 = array.AsExaArray();
Assert.That(exPerf1.OptimizationStrategy, Is.EqualTo(Strategy.MAX_PERFORMANCE));
Assert.That(exPerf1.Length, Is.EqualTo(array.Length));
Assert.That(exPerf1.Items(), Is.EquivalentTo(array));
var exPerf2 = array.AsExaArray(Strategy.MAX_ELEMENTS);
Assert.That(exPerf2.OptimizationStrategy, Is.EqualTo(Strategy.MAX_ELEMENTS));
Assert.That(exPerf2.Length, Is.EqualTo(array.Length));
Assert.That(exPerf2.Items(), Is.EquivalentTo(array));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromSequence003()
{
var list = new List<int>
{
5,
7,
8,
10,
16,
};
var exPerf1 = list.AsEnumerable().AsExaArray();
Assert.That(exPerf1.OptimizationStrategy, Is.EqualTo(Strategy.MAX_PERFORMANCE));
Assert.That(exPerf1.Length, Is.EqualTo(list.Count));
Assert.That(exPerf1.Items(), Is.EquivalentTo(list));
var exPerf2 = list.AsEnumerable().AsExaArray(Strategy.MAX_ELEMENTS);
Assert.That(exPerf2.OptimizationStrategy, Is.EqualTo(Strategy.MAX_ELEMENTS));
Assert.That(exPerf2.Length, Is.EqualTo(list.Count));
Assert.That(exPerf2.Items(), Is.EquivalentTo(list));
}
[Test]
[Category("normal")]
[Category("cover")]
public void CreateFromSequence004()
{
var list = new List<int>
{
5,
7,
8,
10,
16,
};
var exPerf1 = list.AsEnumerable().AsExaArray((ulong) list.Count);
Assert.That(exPerf1.OptimizationStrategy, Is.EqualTo(Strategy.MAX_PERFORMANCE));
Assert.That(exPerf1.Length, Is.EqualTo(list.Count));
Assert.That(exPerf1.Items(), Is.EquivalentTo(list));
var exPerf2 = list.AsEnumerable().AsExaArray((ulong) list.Count, Strategy.MAX_ELEMENTS);
Assert.That(exPerf2.OptimizationStrategy, Is.EqualTo(Strategy.MAX_ELEMENTS));
Assert.That(exPerf2.Length, Is.EqualTo(list.Count));
Assert.That(exPerf2.Items(), Is.EquivalentTo(list));
var exPerf3 = list.AsEnumerable().AsExaArray(3);
Assert.That(exPerf3.OptimizationStrategy, Is.EqualTo(Strategy.MAX_PERFORMANCE));
Assert.That(exPerf3.Length, Is.EqualTo(3));
Assert.That(exPerf3.Items(), Is.EquivalentTo(new int[] {5, 7, 8}));
}
[Test]
[Category("intensive")]
public void Adding5Billion01()
{
ulong sum1 = 0;
@ -86,6 +670,8 @@ namespace ExaArrayTests
}
[Test]
[Category("cover")]
[Category("normal")]
public void Using5Billion01()
{
var exaA = new ExaArray1D<byte>();
@ -94,6 +680,8 @@ namespace ExaArrayTests
}
[Test]
[Category("cover")]
[Category("normal")]
public void TestStrategies01()
{
var exaA = new ExaArray1D<byte>();
@ -107,6 +695,7 @@ namespace ExaArrayTests
}
[Test]
[Category("performance")]
public void TestPerformance01()
{
var t1Times = new long[10];
@ -147,6 +736,62 @@ namespace ExaArrayTests
TestContext.WriteLine($"Performing 100M assignments took {t2Times.Average()} ms (average) by means of the max. performance strategy (min={t2Times.Min()} ms, max={t2Times.Max()} ms)");
}
[Test]
[Category("cover")]
[Category("normal")]
public void StoreAndLoad01()
{
var exaA = new ExaArray1D<byte>();
exaA.Extend(5_000_000);
exaA[4_483_124] = 0xff;
var filename = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
using (var file = File.OpenWrite(filename))
{
exaA.Store(file);
}
using (var file = File.OpenRead(filename))
{
var exaB = ExaArray1D<byte>.Restore(file);
Assert.That(exaA.Length, Is.EqualTo(exaB.Length));
Assert.That(exaA[4_483_124], Is.EqualTo(0xff));
Assert.That(exaB[4_483_124], Is.EqualTo(0xff));
}
File.Delete(filename);
}
[Test]
[Category("cover")]
[Category("normal")]
public void StoreAndLoad02()
{
var exaA = new ExaArray1D<TestClass>();
exaA.Extend(100);
exaA[66] = new TestClass
{
Age = 55,
};
var filename = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
using (var file = File.OpenWrite(filename))
{
exaA.Store(file);
}
using (var file = File.OpenRead(filename))
{
var exaB = ExaArray1D<TestClass>.Restore(file);
Assert.That(exaA.Length, Is.EqualTo(exaB.Length));
Assert.That(exaA[66].Age, Is.EqualTo(55));
Assert.That(exaB[66].Age, Is.EqualTo(55));
}
File.Delete(filename);
}
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Exa;
using NUnit.Framework;
namespace ExaArrayTests
{
[ExcludeFromCodeCoverage]
public class ExaArray2DTests
{
[Test]
[Category("cover")]
[Category("normal")]
public void CreatingNormalSize01()
{
var arr = new ExaArray2D<int>();
// Empty array must have length = 0:
Assert.That(arr.Length, Is.EqualTo(0));
// Try to access some points, which should return default(T):
Assert.That(arr[0, 0], Is.EqualTo(default(int)));
Assert.That(arr[1_000_000, 1_000_000], Is.EqualTo(default(int)));
// Even after accessing some points, there should be no space allocated i.e. length is still 0:
Assert.That(arr.Length, Is.EqualTo(0));
// Write an int to a position:
arr[500, 500] = 4_756;
// Now, we have 500 empty "rows" + 1 "row" with 501 (0-500) elements:
Assert.That(arr.Length, Is.EqualTo(501));
// Should be possible to read out the value:
Assert.That(arr[500, 500], Is.EqualTo(4_756));
// Change the value:
arr[500, 500] = 100;
Assert.That(arr[500, 500], Is.EqualTo(100));
// Still the same size:
Assert.That(arr.Length, Is.EqualTo(501));
// Add another value in the same "row":
arr[500, 499] = 499;
Assert.That(arr[500, 499], Is.EqualTo(499));
Assert.That(arr[500, 500], Is.EqualTo(100));
// Now, we should have still 501 elements, because
// we added the new value "below" the previously:
Assert.That(arr.Length, Is.EqualTo(501));
// Add another value in the same "row", but higher:
arr[500, 1_000] = 6;
Assert.That(arr[500, 499], Is.EqualTo(499));
Assert.That(arr[500, 500], Is.EqualTo(100));
Assert.That(arr[500, 1_000], Is.EqualTo(6));
// Now we should have more elements:
Assert.That(arr.Length, Is.EqualTo(1_001));
}
[Test]
[Category("cover")]
[Category("normal")]
public void CreatingHugeSize01()
{
var arr = new ExaArray2D<int>();
arr[0, 1_000_000] = 47;
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
arr[1, UInt64.MaxValue - 1] = 6;
});
}
[Test]
[Category("cover")]
[Category("normal")]
public void StoreAndLoad01()
{
var exaA = new ExaArray2D<byte>();
exaA[5_000_124, 5_000_666] = 0xff;
var filename = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
using (var file = File.OpenWrite(filename))
{
exaA.Store(file);
}
using (var file = File.OpenRead(filename))
{
var exaB = ExaArray2D<byte>.Restore(file);
Assert.That(exaA.Length, Is.EqualTo(exaB.Length));
Assert.That(exaA[5_000_124, 5_000_666], Is.EqualTo(0xff));
Assert.That(exaB[5_000_124, 5_000_666], Is.EqualTo(0xff));
}
File.Delete(filename);
}
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@ -1,7 +1,6 @@
# ExaArray
ExaArray is a .NET library for exa-scale array-like structures. By using this library, it becomes possible to
add up to 4.6 quintillion i.e. 4,607,183,514,018,780,000 elements into one array. When using `byte` for `T`,
this would need approx. 4 EB of memory.
ExaArray is a .NET library for exa-scale array-like structures. By using this library, it becomes possible to add up to 4.6 quintillion i.e. 4,607,183,514,018,780,000 elements into a one-dimensional array. When using `byte` for `T`, this would need approx. 4 EB of memory. The two-dimensional array can grow up to 18.4 quintillion i.e. 18,446,744,073,709,551,615 elements, though.
Extending the data structure performs as O(n) with O(m+n) of memory. Accessing the data performs as O(1), though.
For the generic type `T`, any .NET type can be used: The ExaArray uses managed memory.
Extending the data structure performs as O(n) with O(m+n) of memory. Accessing the data performs as O(1), though. For the generic type `T`, any .NET type can be used: The ExaArray uses managed memory.
Storing to and loading from an arbitrary stream is supported. The data stored in this way should never be part of a public API. Serializing and deserializing is not secure: an attacker can manipulate the data in a targeted manner to compromise the API server, etc.