C# 获取数组最后N个元素的最有效方法

C# 获取数组最后N个元素的最有效方法,c#,arrays,performance,linq,C#,Arrays,Performance,Linq,对于一个项目,我必须经常获取包含大量数据的数组的最后N个元素 我试着 myArray.Skip(myArray.Length - toTake).Take(toTake) 但我发现它很慢 我把它比作: public static int[] TakeLast(this int[] inputArray, int count) { int[] returnArray = new int[count]; int startIndex = Math.Max(inputArray.Co

对于一个项目,我必须经常获取包含大量数据的数组的最后N个元素

我试着

myArray.Skip(myArray.Length - toTake).Take(toTake)
但我发现它很慢

我把它比作:

public static int[] TakeLast(this int[] inputArray, int count)
{
    int[] returnArray = new int[count];
    int startIndex = Math.Max(inputArray.Count() - count, 0);
    unsafe
    {
        fixed (int* itemArrayPtr = &(inputArray[startIndex]))
        {
            fixed (int* arrayPtr = &(returnArray[0]))
            {
                int* itemValuePtr = itemArrayPtr;
                int* valuePtr = arrayPtr;

                for (int i = 0; i < count; i++)
                {
                    *valuePtr++ = *itemValuePtr++;
                }
            }
        }
    }
    return returnArray;
}

这个怎么样?应该相当快:

public static class ArrayExt
{
    public static T[] TakeLast<T>(this T[] inputArray, int count) where T: struct
    {
        count = Math.Min(count, inputArray.Length);
        int size = Marshal.SizeOf(typeof(T));

        T[] result = new T[count];
        Buffer.BlockCopy(inputArray, (inputArray.Length-count)*size, result, 0, count*size);

        return result;
    }
}
Array.Copy()
更简单,因此这就是要使用的方法。

来自注释:

public static T[] TakeLast<T>(this T[] inputArray, int count)
{
    var result = new T[count];
    Array.Copy(inputArray, inputArray.Length - count, result, 0, count);
    return result;
}
public static T[]TakeLast(此T[]inputArray,int count)
{
var结果=新的T[计数];
Copy(inputArray,inputArray.Length-count,result,0,count);
返回结果;
}
似乎表现不错。值得指出的是,根据具体需要,可以完全避免使用新的数组,并在原始的
inputArray
上进行迭代。你复制的速度不可能比根本不复制快。:)

既然C#8你就可以用一个类似的

var lastN=array[^n..];

新建T[count]
后接
数组。是否复制?顺便说一句,一个简短的程序,演示如何
可枚举。Skip
/
可枚举。Take
很慢会很好,这将使实际测试建议的替代方案成为可能,而不是主要猜测。是的,我原以为Array.Copy()会像做指针算术一样快,因为它可能在实现中使用了memcpy等价物。我不认为这会有明显的区别,但是您对
Take()
的调用是不必要的。如果只使用myArray.Skip(myArray.Length-toTake)
,性能会有很大提高吗?@hvd:请将此作为答案发布。真是太快了is@BACON很好的一点,我测试它时没有使用
Take()
,但是看起来仍然很慢,但是为什么这比Array.Copy快呢?现在我的测试没有显示它更快。@J4N它没有更快-我只是试了一下。Array.Copy()更快。我会在我的答案中加入免责声明-您需要发布您的数组。Copy()答案:)是的,但我想奖励这个解决方案附带的hvc。因此,如果他不发布,我会发布。@J4N哦,对不起,是的,我的意思是hvd应该发布它(我错说了“你”)似乎比Skip快一点。需要,但仍然需要很多时间+1我的测试表明这是最好的解决方案,依我看。也许你应该添加
count=Math.Min(count,inputArray.Length)开始。谢谢,在我所有的测试之后,这是更好的解决方案。如何避免使用新阵列?(在我的例子中,我可以直接引用基本数组,原始数组或目标数组都不会更改,只读操作)@J4N您可以创建一个自定义
IEnumerable
,其中包含对原始数组的引用,以及对该数组的索引。这类似于
Enumerable.Skip
的工作方式,但首先,因为您知道您正在处理数组,所以可以通过索引访问数组元素,其次,您有足够的额外信息来避免不必要的装箱操作。这是否比复制数据更好取决于所涉及的数据。你也可以使用哪个实现了
IEnumerable
(但我认为你需要.Net 4.5来实现IEnumerable功能)@MatthewWatson Ha,我不知道存在这样的功能。这正是我所建议的,唯一的区别是,根据数据的不同,添加一个返回值类型的
GetEnumerator()
方法可能会有所帮助。:)
public static class ArrayExt
{
    public static T[] TakeLast<T>(this T[] inputArray, int count) where T: struct
    {
        count = Math.Min(count, inputArray.Length);
        int size = Marshal.SizeOf(typeof(T));

        T[] result = new T[count];
        Buffer.BlockCopy(inputArray, (inputArray.Length-count)*size, result, 0, count*size);

        return result;
    }
}
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

namespace Demo
{
    internal class Program
    {
        private void run()
        {
            const int ARRAY_SIZE = 10000;
            var array = Enumerable.Range(0, ARRAY_SIZE).Select(x => x).ToArray();
            Stopwatch sw = new Stopwatch();
            const int COUNT = 100000;

            for (int i = 0; i < 8; ++i)
            {
                sw.Restart();

                for (int j = 0; j < COUNT; ++j)
                    array.TakeLastViaArrayCopy(ARRAY_SIZE/2);

                Console.WriteLine("TakeLastViaArrayCopy took " + sw.Elapsed);

                sw.Restart();

                for (int j = 0; j < COUNT; ++j)
                    array.TakeLastViaBlockCopy(ARRAY_SIZE/2);

                Console.WriteLine("TakeLastViaBlockCopy took " + sw.Elapsed);
                Console.WriteLine();
            }
        }

        private static void Main()
        {
            new Program().run();
        }
    }

    public static class ArrayExt
    {
        public static T[] TakeLastViaBlockCopy<T>(this T[] inputArray, int count) where T: struct
        {
            count = Math.Min(count, inputArray.Length);
            int size = Marshal.SizeOf(typeof(T));

            T[] result = new T[count];
            Buffer.BlockCopy(inputArray, (inputArray.Length-count)*size, result, 0, count*size);

            return result;
        }

        public static T[] TakeLastViaArrayCopy<T>(this T[] inputArray, int count) where T: struct
        {
            count = Math.Min(count, inputArray.Length);

            T[] result = new T[count];
            Array.Copy(inputArray, inputArray.Length-count, result, 0, count);

            return result;
        }
    }
}
TakeLastViaArrayCopy took 00:00:00.3028503
TakeLastViaBlockCopy took 00:00:00.3052196

TakeLastViaArrayCopy took 00:00:00.2969425
TakeLastViaBlockCopy took 00:00:00.3000117

TakeLastViaArrayCopy took 00:00:00.2906120
TakeLastViaBlockCopy took 00:00:00.2987753

TakeLastViaArrayCopy took 00:00:00.2954674
TakeLastViaBlockCopy took 00:00:00.3005010

TakeLastViaArrayCopy took 00:00:00.2944490
TakeLastViaBlockCopy took 00:00:00.3006893

TakeLastViaArrayCopy took 00:00:00.3041998
TakeLastViaBlockCopy took 00:00:00.2920206

TakeLastViaArrayCopy took 00:00:00.3115137
TakeLastViaBlockCopy took 00:00:00.2996884

TakeLastViaArrayCopy took 00:00:00.2906820
TakeLastViaBlockCopy took 00:00:00.2985933
public static T[] TakeLast<T>(this T[] inputArray, int count)
{
    var result = new T[count];
    Array.Copy(inputArray, inputArray.Length - count, result, 0, count);
    return result;
}