【问题标题】:ArraySegment - Returning the actual segment C#ArraySegment - 返回实际的段 C#
【发布时间】:2011-08-11 00:45:37
【问题描述】:

我一直在寻找返回基本上由 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不能是副本,而应该引用原始数组。如果对段进行了任何更改,它们必须反映在原始数组中。

非常感谢任何提示,谢谢!

分配基准:在ThomasdigEmAll 的一些回答之后

好的,我对来自 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    

}

访问基准(更新) 因此,在Thomas 指出基准并表示访问简单数组会比 ArraySegment 更快之后,他完全正确。但是随着 digEmAll 指出我应该在发布模式下进行测试(对不起,在调试模式下测试的旧错误),我留下的代码几乎与上面相同(迭代减少了两个零 - 不能等待很长时间才能输出来吧,对不起)和一些修改以访问相同数量的元素,下面是我得到的。

Construct             Size    Elements accessed    Iterations       Time
_______________________________________________________________________________

ArraySegmentWrapper   1500        1500              1000000       5268.3 ms
Array.Copy            1500        1500              1000000       4812.4 ms

得出的结论是虽然assingment非常快,但通过ArraySegments访问却很慢。

【问题讨论】:

  • 您的基准测试并没有真正意义:您只是创建了一个 ArraySegmentWrapper 实例,它不执行任何实际工作,因此它当然比在两个数组之间复制数据要快.​​..您应该衡量的是通过 ArraySegmentWrapper 访问数据与直接在复制的数组中访问数据的性能。数组可能会更快,但当然你必须先复制数据,这不是免费的。所以你必须在创建时间和访问时间之间做出选择……
  • @Thomas,那么当我创建 ArraySegmentWrapper 的实例时,段不是也在创建吗?将段放入arraySeg。据我所知,这就是工作正在进行的地方。
  • @Wajih,创建该段的成本 nothing :它只存储对数组的引用、偏移量和计数......它实际上并不 做任何事
  • @Thomas,准确地说,准确,这就是我们所需要的 :)
  • @Wajih:好吧,优化器和抖动毕竟可以发挥作用:D

标签: c# arrays segment


【解决方案1】:

Thomas Levesque's suggestion 开始,我构建了一个简单的ArraySegmentWrapper&lt;T&gt; 类,以这种方式使用:

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 在 cmets 中指出的那样,由于 .NET 4.5 ArraySegment&lt;T&gt; 实现了 IList&lt;T&gt;

【讨论】:

  • 嗯,它对我来说是一个宝石,我现在会做一些分析,看看我能想出什么。
  • 为什么使用 ArraySegment 作为支持类?该类只有 20 行代码——由于必须完成所有取消引用,您会导致大量性能损失。此外,您还有冗余代码检查数组索引等。我将您的代码修改为仅使用数组引用,并且访问它的速度与使用普通数组的速度非常接近,因为它不必进行双重和三重取消引用并不断访问属性获取器。我知道这些是微优化,但在某些情况下它们确实很重要。
  • @RepDbg:这只是一个例子。当然,由于 ArraySegment 非常简单,您可以轻松地直接在包装器中实现它。无论如何,我几乎不认为性能会发生很大变化,因为编译器和抖动通常会内联很多方法调用。可能仅考虑相对性能增益差异会很大,但从绝对角度来看,我们正在谈论秒的碎屑...... :)
  • 我真的很喜欢这个包装!我正在尝试找到将 ArraySegment 传递给 Sha-256 散列类的最佳方法。这意味着我可以将其转换为数组或流。我不确定哪个开销更少……或者它们最终是否相同……
  • 注意:自 2012 年 8 月的 .NET 4.5 起,ArraySegment&lt;&gt; struct 本身就实现了IList&lt;&gt;
【解决方案2】:

我使用以下一组扩展方法来处理数组段:

    #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();

【讨论】:

  • 这不是片段的副本吗?这意味着对段的任何更改都不会反映在原始数组中?
  • 是的,这将是一个副本。这不是你想要的吗?没有办法让不同的数组指向相同的数据...但是您可以创建 IList 的实现来访问数组的一部分。
  • 嗯,我想我会非常想念C!还是谢谢!
  • @Wajih:C# 与 C 不同,你不能拥有一个实际上是另一个子数组的数组,因为你不能(实际上不应该)使用指针。您可以按照 Thomas 的建议创建一个自定义的 IList&lt;&gt; 实现,将原始数组实例保留在内部(注意,所有 T[] 数组都实现 IList&lt;T&gt;)。
  • @Wajih:以我的回答为例;)
【解决方案3】:

C#(和一般的 .NET)不允许您创建“指向”另一个数组内部的标准数组引用。因此,您要么需要更改使用 API 以便它们可以处理 ArraySegment 实例,要么您需要创建数据的副本,然后在对副本进行操作后将更改复制回来。无论如何,这通常是一种更安全的方法,因为传递对数组的引用会破坏绝缘,并且随着数组消费者数量的增加,更难以追踪错误。在 .NET 中构造新的数组实例和复制值相对便宜,只要数组的大小不是特别大,因此这里的性能影响通常可以忽略不计。

如果您遇到性能问题并且需要进行微优化,我建议您使用不安全的 C# 代码(您可以在其中修复数组引用并传递指针)或将性能关键代码提取到C++/CLI 程序集,您可以在其中使用非托管内存进行计算。我建议首先分析代码以验证这确实是您的瓶颈。我不能再强调你不应该担心在 .NET 中分配新内存,因为压缩 GC 堆的性质意味着频繁的小分配比在 C 中更便宜(内存分配必须适应可能的堆碎片.)

【讨论】:

  • 我做了一些分析,发现 ArraySegment out 执行 Array.Copy 的倍数是 2 倍。我使用 Array.Copy 创建了一个段。我需要一些参考原始数据的快速代码。这就是我探索 ArraySegment 的原因。唯一的问题是段引用,Array.Copy 必须从代码中提取出来,因为它的速度很慢并且没有引用数组切片。不过,感谢您的回答。我将探索更多的东西。
【解决方案4】:

查看我在此主题上发布的答案here

基本上,您只需将 ArraySegment 转换为 IList 即可获得您期望的功能。

【讨论】:

  • 注意:此线程来自 2011 年,来自 .NET 4.5 之前。在那个时候,ArraySegment&lt;&gt; struct 没有实现任何接口。这种情况在 2012 年 8 月 .NET 4.5 发布时发生了变化。
猜你喜欢
  • 2011-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多