C# 获取数组最后N个元素的最有效方法
对于一个项目,我必须经常获取包含大量数据的数组的最后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
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;
}