ArraySegment -返回实际的c#段
本文关键字:返回 ArraySegment | 更新日期: 2023-09-27 17:52:57
我一直在寻找返回基本上由ArraySegment在偏移量和计数方面持有的段的方法。虽然ArraySegment保存了完整的原始数组,但它只是用对段的任何更改都反映到原始数组中这一事实来分隔它。问题或者说限制与ArraySegment是,它不会返回段本身作为一个整体,我必须遍历值。返回整个片段的最佳方式是什么?
byte[] input = new byte[5]{1,2,3,4,5};
ArraySegment<byte> delimited = new ArraySegment<byte>(input,0,2);
byte[] segment = HERE I NEED SOMETHING THAT WILL RETURN THE SEGMENT i.e. [0,1,2]
最重要的一点是,segment不能是一个副本,而应该指向原始数组。如果对segment进行了任何更改,则必须在原始数组中反映出来。
任何提示都非常感谢,谢谢!
ASSIGNMENT benchmark : After some answer from Thomas and digEmAll
好吧,我对digEmAll和Thomas的代码运行了一些基准测试,令我惊讶的是,代码要快得多。这正是我梦寐以求的。以下是结果。
Construct Size Elements assigned Iterations Time
_______________________________________________________________________________
ArraySegmentWrapper 1500 1500 1000000 396.3 ms
Array.Copy 1500 1500 1000000 4389.04 ms
正如你所看到的巨大差异,我很清楚,我将使用的代码为ArraySegment。下面是基准测试代码。请注意,这可能有点人们会有偏见地争论为什么"新"被放在一个循环中。我只是想重现我目前手头的情况,在不移动太多代码的情况下尽可能多地解决它。这让我很开心!
namespace ArraySegmentWrapped
{
class Program
{
public static Stopwatch stopWatch = new Stopwatch();
public static TimeSpan span = new TimeSpan();
public static double totalTime = 0.0;
public static int iterations = 1000000;
static void Main(string[] args)
{
int size = 1500;
int startIndex = 0;
int endIndex = 1499;
byte[] array1 = new byte[size];
byte[] array2 = null;
for (int index = startIndex; index < size; index++)
{
array1[index] = (byte)index;
}
ArraySegmentWrapper<byte> arraySeg;
for (int index = 0; index < iterations; index++)
{
stopWatch.Start();
arraySeg = new ArraySegmentWrapper<byte>(array1, startIndex, endIndex);
stopWatch.Stop();
totalTime += stopWatch.Elapsed.TotalMilliseconds;
}
Console.WriteLine("ArraySegment:{0:F6}", totalTime / iterations);
stopWatch.Reset();
totalTime = 0.0;
for (int index = 0; index < iterations; index++)
{
stopWatch.Start();
array2 = new byte[endIndex - startIndex + 1];
Array.Copy(array1, startIndex, array2, 0, endIndex);
stopWatch.Stop();
totalTime += stopWatch.Elapsed.TotalMilliseconds;
}
Console.WriteLine("Array.Copy:{0:F6}", totalTime / iterations);
}
}
// Code for ArraySegmentWrapper goes here
}
ACCESS benchmark (Updated)所以在Thomas指出基准测试并说访问简单数组的速度要比ArraySegment快之后,他是完全正确的。但是digEmAll指出我应该在发布模式下测试(抱歉在调试模式下测试的旧错误),我留下的代码几乎与上面相同(迭代减少了两个零-不能等待很长时间的输出,抱歉),并进行了一些修改以访问相同数量的元素,下面是我得到的。
Construct Size Elements accessed Iterations Time
_______________________________________________________________________________
ArraySegmentWrapper 1500 1500 1000000 5268.3 ms
Array.Copy 1500 1500 1000000 4812.4 ms
得出结论,虽然赋值非常快,但通过ArraySegments访问很慢。
从Thomas Levesque的建议开始,我建立了一个简单的ArraySegmentWrapper<T>
类,以这种方式使用:
static void Main(string[] args)
{
int[] arr = new int[10];
for (int i = 0; i < arr.Length; i++)
arr[i] = i;
// arr = 0,1,2,3,4,5,6,7,8,9
var segment = new ArraySegmentWrapper<int>(arr, 2, 7);
segment[0] = -1;
segment[6] = -1;
// now arr = 0,1,-1,3,4,5,6,7,-1,9
// this prints: -1,3,4,5,6,7,-1
foreach (var el in segment)
Console.WriteLine(el);
}
实现:
public class ArraySegmentWrapper<T> : IList<T>
{
private readonly ArraySegment<T> segment;
public ArraySegmentWrapper(ArraySegment<T> segment)
{
this.segment = segment;
}
public ArraySegmentWrapper(T[] array, int offset, int count)
: this(new ArraySegment<T>(array, offset, count))
{
}
public int IndexOf(T item)
{
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
if (Equals(segment.Array[i], item))
return i;
return -1;
}
public void Insert(int index, T item)
{
throw new NotSupportedException();
}
public void RemoveAt(int index)
{
throw new NotSupportedException();
}
public T this[int index]
{
get
{
if (index >= this.Count)
throw new IndexOutOfRangeException();
return this.segment.Array[index + this.segment.Offset];
}
set
{
if (index >= this.Count)
throw new IndexOutOfRangeException();
this.segment.Array[index + this.segment.Offset] = value;
}
}
public void Add(T item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public bool Contains(T item)
{
return this.IndexOf(item) != -1;
}
public void CopyTo(T[] array, int arrayIndex)
{
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
{
array[arrayIndex] = segment.Array[i];
arrayIndex++;
}
}
public int Count
{
get { return this.segment.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
throw new NotSupportedException();
}
public IEnumerator<T> GetEnumerator()
{
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
yield return segment.Array[i];
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
编辑:
正如@JeppeStigNielsen在评论中指出的,自从。net 4.5 ArraySegment<T>
实现了IList<T>
我使用以下一组扩展方法来处理数组段:
#region ArraySegment related methods
public static ArraySegment<T> GetSegment<T>(this T[] array, int from, int count)
{
return new ArraySegment<T>(array, from, count);
}
public static ArraySegment<T> GetSegment<T>(this T[] array, int from)
{
return GetSegment(array, from, array.Length - from);
}
public static ArraySegment<T> GetSegment<T>(this T[] array)
{
return new ArraySegment<T>(array);
}
public static IEnumerable<T> AsEnumerable<T>(this ArraySegment<T> arraySegment)
{
return arraySegment.Array.Skip(arraySegment.Offset).Take(arraySegment.Count);
}
public static T[] ToArray<T>(this ArraySegment<T> arraySegment)
{
T[] array = new T[arraySegment.Count];
Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
return array;
}
#endregion
你可以这样使用它们:
byte[] input = new byte[5]{1,2,3,4,5};
ArraySegment<byte> delimited = input.GetSegment(0, 2);
byte[] segment = delimited.ToArray();
c#(和一般的。net)不允许你创建一个"指向"另一个数组内部的标准数组引用。因此,您要么需要更改您的消费api,以便它们可以处理ArraySegment实例,要么需要创建数据的副本,然后在对副本进行操作后将更改复制回来。无论如何,这通常是一种更安全的方法,因为传递对数组的引用会打破隔离,并且随着数组消费者数量的增加,跟踪bug变得更加困难。在。net中,构造新的数组实例和复制值相对便宜,只要数组的大小不是特别大,因此这里的性能影响通常可以忽略不计。
如果你遇到性能问题,你需要微优化,我建议使用不安全的c#代码(在那里你可以修复数组引用和传递指针),或者把性能关键代码拉到c++/CLI程序集,在那里你可以用非托管内存进行计算。我建议首先对代码进行分析,以验证这确实是您的瓶颈。我必须强调,您不应该害怕在。net中分配新内存,因为压缩GC堆的性质意味着频繁的小分配比在C中更便宜(在C中,内存分配必须适应可能的堆碎片)。
查看我在这里发布的关于这个主题的答案
基本上你所要做的就是将ArraySegment转换为IList来获得你期望的功能