Skip to content

Commit

Permalink
Implemented typed reduce and aggregate methods
Browse files Browse the repository at this point in the history
  • Loading branch information
kenkendk committed Apr 29, 2015
1 parent 16ae5ca commit 4b77e14
Show file tree
Hide file tree
Showing 6 changed files with 572 additions and 244 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,5 @@ bridge/NumCIL/NumCIL.Unsafe/ApplyUnary.cs
bridge/NumCIL/NumCIL/UFunc/TypedApplyBinary.cs
bridge/NumCIL/NumCIL/UFunc/TypedApplyNullary.cs
bridge/NumCIL/NumCIL/UFunc/TypedApplyUnary.cs
bridge/NumCIL/NumCIL/UFunc/TypedAggregate.cs
bridge/NumCIL/NumCIL/UFunc/TypedReduce.cs
14 changes: 14 additions & 0 deletions bridge/NumCIL/NumCIL/NumCIL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@
<Compile Include="UFunc\TypedApplyBinary.cs">
<DependentUpon>TypedApplyBinary.tt</DependentUpon>
</Compile>
<Compile Include="UFunc\TypedReduce.cs">
<DependentUpon>TypedReduce.tt</DependentUpon>
</Compile>
<Compile Include="UFunc\TypedAggregate.cs">
<DependentUpon>TypedAggregate.tt</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="TypedArrays.tt">
Expand All @@ -96,6 +102,14 @@
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>TypedApplyNullary.cs</LastGenOutput>
</None>
<None Include="UFunc\TypedReduce.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>TypedReduce.cs</LastGenOutput>
</None>
<None Include="UFunc\TypedAggregate.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>TypedAggregate.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
Expand Down
160 changes: 38 additions & 122 deletions bridge/NumCIL/NumCIL/UFunc/Aggregate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,127 +114,43 @@ public static T Aggregate<T>(IBinaryOp<T> op, NdArray<T> in1)
}
}

/// <summary>
/// Calculates the scalar result of applying the binary operation to all elements
/// </summary>
/// <typeparam name="T">The value to operate on</typeparam>
/// <typeparam name="C">The operation to perform</typeparam>
/// <param name="op">The operation to perform</param>
/// <param name="in1">The array to aggregate</param>
/// <returns>A scalar value that is the result of aggregating all elements</returns>
internal static T UFunc_Aggregate_Inner_Flush<T, C>(C op, NdArray<T> in1)
where C : struct, IBinaryOp<T>
{
T result;
if (UnsafeAPI.Aggregate_Entry_Unsafe<T, C>(op, in1, out result))
return result;

T[] d1 = in1.AsArray();

if (in1.Shape.Dimensions.Length == 1)
{
long totalOps = in1.Shape.Dimensions[0].Length;
long ix1 = in1.Shape.Offset;
long stride1 = in1.Shape.Dimensions[0].Stride;

result = d1[ix1];
ix1 += stride1;

for (long i = 1; i < totalOps; i++)
{
result = op.Op(result, d1[ix1]);
ix1 += stride1;
}
}
else if (in1.Shape.Dimensions.Length == 2)
{
long opsOuter = in1.Shape.Dimensions[0].Length;
long opsInner = in1.Shape.Dimensions[1].Length;

long ix1 = in1.Shape.Offset;
long outerStride1 = in1.Shape.Dimensions[0].Stride;
long innerStride1 = in1.Shape.Dimensions[1].Stride;
outerStride1 -= innerStride1 * in1.Shape.Dimensions[1].Length;

result = d1[ix1];
ix1 += innerStride1;

for (long i = 0; i < opsOuter; i++)
{
for (long j = (i == 0 ? 1 : 0); j < opsInner; j++)
{
result = op.Op(result, d1[ix1]);
ix1 += innerStride1;
}

ix1 += outerStride1;
}
}
else
{
long n = in1.Shape.Dimensions.LongLength - 3;
long[] limits = in1.Shape.Dimensions.Where(x => n-- > 0).Select(x => x.Length).ToArray();
long[] counters = new long[limits.LongLength];

long totalOps = limits.LongLength == 0 ? 1 : limits.Aggregate<long>((a, b) => a * b);

//This chunck of variables are used to prevent repeated calculations of offsets
long dimIndex0 = 0 + limits.LongLength;
long dimIndex1 = 1 + limits.LongLength;
long dimIndex2 = 2 + limits.LongLength;

long opsOuter = in1.Shape.Dimensions[0 + limits.LongLength].Length;
long opsInner = in1.Shape.Dimensions[1 + limits.LongLength].Length;
long opsInnerInner = in1.Shape.Dimensions[2 + limits.LongLength].Length;

long outerStride1 = in1.Shape.Dimensions[dimIndex0].Stride;
long innerStride1 = in1.Shape.Dimensions[dimIndex1].Stride;
long innerInnerStride1 = in1.Shape.Dimensions[dimIndex2].Stride;

outerStride1 -= innerStride1 * in1.Shape.Dimensions[dimIndex1].Length;
innerStride1 -= innerInnerStride1 * in1.Shape.Dimensions[dimIndex2].Length;

result = d1[in1.Shape[counters]];
bool first = true;

for (long outer = 0; outer < totalOps; outer++)
{
//Get the array offset for the first element in the outer dimension
long ix1 = in1.Shape[counters];
if (first)
ix1 += innerInnerStride1;

for (long i = 0; i < opsOuter; i++)
{
for (long j = 0; j < opsInner; j++)
{
for (long k = (first ? 1 : 0); k < opsInnerInner; k++)
{
result = op.Op(result, d1[ix1]);
ix1 += innerInnerStride1;
}
first = false;

ix1 += innerStride1;
}

ix1 += outerStride1;
}

if (counters.LongLength > 0)
{
//Basically a ripple carry adder
long p = counters.LongLength - 1;
while (++counters[p] == limits[p] && p > 0)
{
counters[p] = 0;
p--;
}
}
}
}

return result;
}
/// <summary>
/// Attempts to use a typed version of the Aggregate call,
/// to avoid dependency on the JIT being able to inline struct methods
/// </summary>
/// <typeparam name="T">The type of data to operate on</typeparam>
/// <typeparam name="C">The type of operation to reduce with</typeparam>
/// <param name="op">The instance of the operation to reduce with</param>
/// <param name="in1">The input argument</param>
/// <param name="axis">The axis to reduce</param>
/// <param name="out">The output target</param>
/// <returns>The output target</returns>
private static bool UFunc_Aggregate_Inner_Flush_Typed<T, C>(C op, NdArray<T> in1, out T @out)
{
System.Reflection.MethodInfo f;
var key = typeof(T).FullName + "#" + op.GetType().FullName + "#AGR";
if (!_resolvedMethods.TryGetValue(key, out f))
{
var n = typeof(UFunc).GetMethod("UFunc_Aggregate_Inner_Flush",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static,
null,
new Type[] {
op.GetType(),
in1.GetType()
}, null);

_resolvedMethods[key] = f = n == null ? null : n.MakeGenericMethod(new Type[] { typeof(T), op.GetType() });
}

if (f != null)
{
@out = (T)f.Invoke(null, new object[] { op, in1 });
return true;
}
else
@out = default(T);

return false;
}
}
}
160 changes: 38 additions & 122 deletions bridge/NumCIL/NumCIL/UFunc/Reduce.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,128 +161,44 @@ public static NdArray<T> Reduce<T>(IBinaryOp<T> op, NdArray<T> in1, long axis =
return (NdArray<T>)gm.Invoke(null, new object[] { op, in1, axis, @out });
}

/// <summary>
/// Actually executes a reduce operation in CIL by retrieving the data and executing the <see cref="T:NumCIL.IBinaryOp{0}"/> on each element in the given dimension.
/// This implementation is optimized for use with up to 2 dimensions, but works for any size dimension.
/// This method is optimized for 64bit processors, using the .Net 4.0 runtime.
/// </summary>
/// <typeparam name="T">The type of data to operate on</typeparam>
/// <typeparam name="C">The type of operation to reduce with</typeparam>
/// <param name="op">The instance of the operation to reduce with</param>
/// <param name="in1">The input argument</param>
/// <param name="axis">The axis to reduce</param>
/// <param name="out">The output target</param>
/// <returns>The output target</returns>
private static NdArray<T> UFunc_Reduce_Inner_Flush<T, C>(C op, long axis, NdArray<T> in1, NdArray<T> @out)
where C : struct, IBinaryOp<T>
{
if (UnsafeAPI.UFunc_Reduce_Inner_Flush_Unsafe<T, C>(op, axis, in1, @out))
return @out;

if (axis < 0)
axis = in1.Shape.Dimensions.LongLength - axis;

//Basic case, just return a reduced array
if (in1.Shape.Dimensions[axis].Length == 1 && in1.Shape.Dimensions.LongLength > 1)
{
//TODO: If both in and out use the same array, just return a reshaped in
long j = 0;
var sizes = in1.Shape.Dimensions.Where(x => j++ != axis).ToArray();
UFunc_Op_Inner_Unary_Flush<T, CopyOp<T>>(new CopyOp<T>(), in1.Reshape(new Shape(sizes, in1.Shape.Offset)), @out);
}
else
{
T[] d = in1.AsArray();
T[] vd = @out.AsArray();

//Simple case, reduce 1D array to scalar value
if (axis == 0 && in1.Shape.Dimensions.LongLength == 1)
{
long stride = in1.Shape.Dimensions[0].Stride;
long ix = in1.Shape.Offset;
long limit = (stride * in1.Shape.Dimensions[0].Length) + ix;

T value = d[ix];

for (long i = ix + stride; i < limit; i += stride)
value = op.Op(value, d[i]);

vd[@out.Shape.Offset] = value;
}
//Simple case, reduce 2D array to 1D
else if (axis == 0 && in1.Shape.Dimensions.LongLength == 2)
{
long strideInner = in1.Shape.Dimensions[1].Stride;
long strideOuter = in1.Shape.Dimensions[0].Stride;

long ix = in1.Shape.Offset;
long ox = @out.Shape.Offset;
long strideRes = @out.Shape.Dimensions[0].Stride;

long outerCount = in1.Shape.Dimensions[0].Length;

for (long i = 0; i < in1.Shape.Dimensions[1].Length; i++)
{
T value = d[ix];

long nx = ix;
for (long j = 1; j < outerCount; j++)
{
nx += strideOuter;
value = op.Op(value, d[nx]);
}

vd[ox] = value;
ox += strideRes;

ix += strideInner;
}
}
//Simple case, reduce 2D array to 1D
else if (axis == 1 && in1.Shape.Dimensions.LongLength == 2)
{
long strideInner = in1.Shape.Dimensions[1].Stride;
long strideOuter = in1.Shape.Dimensions[0].Stride;

long ix = in1.Shape.Offset;
long limitInner = strideInner * in1.Shape.Dimensions[1].Length;

long ox = @out.Shape.Offset;
long strideRes = @out.Shape.Dimensions[0].Stride;

for (long i = 0; i < in1.Shape.Dimensions[0].Length; i++)
{
T value = d[ix];

for (long j = strideInner; j < limitInner; j += strideInner)
value = op.Op(value, d[j + ix]);

vd[ox] = value;
ox += strideRes;

ix += strideOuter;
}
}
//General case
else
{
long size = in1.Shape.Dimensions[axis].Length;
NdArray<T> vl = @out.Subview(Range.NewAxis, axis);

//Initially we just copy the value
UFunc_Op_Inner_Unary_Flush<T, CopyOp<T>>(new CopyOp<T>(), in1.Subview(Range.El(0), axis), vl);

//If there is more than one element in the dimension to reduce, apply the operation accumulatively
for (long j = 1; j < size; j++)
{
//Select the new dimension
//Apply the operation
UFunc_Op_Inner_Binary_Flush<T, C>(op, vl, in1.Subview(Range.El(j), axis), vl);
}
}
}
return @out;
}
/// <summary>
/// Attempts to use a typed version of the Reduce call,
/// to avoid dependency on the JIT being able to inline struct methods
/// </summary>
/// <typeparam name="T">The type of data to operate on</typeparam>
/// <typeparam name="C">The type of operation to reduce with</typeparam>
/// <param name="op">The instance of the operation to reduce with</param>
/// <param name="in1">The input argument</param>
/// <param name="axis">The axis to reduce</param>
/// <param name="out">The output target</param>
/// <returns>The output target</returns>
private static bool UFunc_Reduce_Inner_Flush_Typed<T, C>(C op, long axis, NdArray<T> in1, NdArray<T> @out)
{
System.Reflection.MethodInfo f;
var key = typeof(T).FullName + "#" + op.GetType().FullName + "#RED";
if (!_resolvedMethods.TryGetValue(key, out f))
{
var n = typeof(UFunc).GetMethod("UFunc_Reduce_Inner_Flush",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static,
null,
new Type[] {
op.GetType(),
axis.GetType(),
in1.GetType(),
@out.GetType()
}, null);

_resolvedMethods[key] = f = n == null ? null : n.MakeGenericMethod(new Type[] { typeof(T), op.GetType() });
}

if (f != null)
{
f.Invoke(null, new object[] { op, axis, in1, @out });
return true;
}

return false;
}

}
}

0 comments on commit 4b77e14

Please sign in to comment.