我需要找到两个排序整数数组的交集并快速完成。
现在,我正在使用以下代码:
int i = 0, j = 0;
while (i < arr1.Count && j < arr2.Count)
{
if (arr1[i] < arr2[j])
{
i++;
}
else
{
if (arr2[j] < arr1[i])
{
j++;
}
else
{
intersect.Add(arr2[j]);
j++;
i++;
}
}
}
不幸的是,完成所有工作可能需要数小时。
如何更快地完成?我发现this article使用了SIMD指令。是否可以在.NET中使用SIMD?
您如何看待:
http://docs.go-mono.com/index.aspx?link=N:Mono.Simd Mono.SIMD
http://netasm.codeplex.com/ NetASM(将asm代码注入托管)
等http://www.atrevido.net/blog/PermaLink.aspx?guid=ac03f447-d487-45a6-8119-dc4fa1e932e1
编辑:
当我说成千上万的意思是跟随(在代码中)
for(var i=0;i<arrCollection1.Count-1;i++)
{
for(var j=i+1;j<arrCollection2.Count;j++)
{
Intersect(arrCollection1[i],arrCollection2[j])
}
}
答案 0 :(得分:10)
<强>更新强>
我获得的最快速度为200毫秒,阵列大小为10毫米,带有不安全版本(最后一段代码)。
我做过的测试:
var arr1 = new int[10000000];
var arr2 = new int[10000000];
for (var i = 0; i < 10000000; i++)
{
arr1[i] = i;
arr2[i] = i * 2;
}
var sw = Stopwatch.StartNew();
var result = arr1.IntersectSorted(arr2);
sw.Stop();
Console.WriteLine(sw.Elapsed); // 00:00:00.1926156
全职:
我已经测试了各种方法,并发现这非常好:
public static List<int> IntersectSorted(this int[] source, int[] target)
{
// Set initial capacity to a "full-intersection" size
// This prevents multiple re-allocations
var ints = new List<int>(Math.Min(source.Length, target.Length));
var i = 0;
var j = 0;
while (i < source.Length && j < target.Length)
{
// Compare only once and let compiler optimize the switch-case
switch (source[i].CompareTo(target[j]))
{
case -1:
i++;
// Saves us a JMP instruction
continue;
case 1:
j++;
// Saves us a JMP instruction
continue;
default:
ints.Add(source[i++]);
j++;
// Saves us a JMP instruction
continue;
}
}
// Free unused memory (sets capacity to actual count)
ints.TrimExcess();
return ints;
}
为了进一步改进,您可以移除ints.TrimExcess();
,这也会产生很大的不同,但您应该考虑是否需要该内存。
另外,如果您知道可能会破坏使用交叉点的循环,并且您不必将结果作为数组/列表,则应将实现更改为迭代器:
public static IEnumerable<int> IntersectSorted(this int[] source, int[] target)
{
var i = 0;
var j = 0;
while (i < source.Length && j < target.Length)
{
// Compare only once and let compiler optimize the switch-case
switch (source[i].CompareTo(target[j]))
{
case -1:
i++;
// Saves us a JMP instruction
continue;
case 1:
j++;
// Saves us a JMP instruction
continue;
default:
yield return source[i++];
j++;
// Saves us a JMP instruction
continue;
}
}
}
另一项改进是使用不安全的代码:
public static unsafe List<int> IntersectSorted(this int[] source, int[] target)
{
var ints = new List<int>(Math.Min(source.Length, target.Length));
fixed (int* ptSrc = source)
{
var maxSrcAdr = ptSrc + source.Length;
fixed (int* ptTar = target)
{
var maxTarAdr = ptTar + target.Length;
var currSrc = ptSrc;
var currTar = ptTar;
while (currSrc < maxSrcAdr && currTar < maxTarAdr)
{
switch ((*currSrc).CompareTo(*currTar))
{
case -1:
currSrc++;
continue;
case 1:
currTar++;
continue;
default:
ints.Add(*currSrc);
currSrc++;
currTar++;
continue;
}
}
}
}
ints.TrimExcess();
return ints;
}
总结,最重要的表现是if-else&#39; 把它变成一个开关盒会产生巨大的差异(大约快2倍)。
答案 1 :(得分:2)
你尝试过这样简单的事情:
var a = Enumerable.Range(1, int.MaxValue/100).ToList();
var b = Enumerable.Range(50, int.MaxValue/100 - 50).ToList();
//var c = a.Intersect(b).ToList();
List<int> c = new List<int>();
var t1 = DateTime.Now;
foreach (var item in a)
{
if (b.BinarySearch(item) >= 0)
c.Add(item);
}
var t2 = DateTime.Now;
var tres = t2 - t1;
这段代码包含1个 21,474,836 元素数组,另一个包含 21,474,786
如果我使用var c = a.Intersect(b).ToList();
,我会收到OutOfMemoryException
结果产品使用嵌套foreach 461,167,507,485,096 迭代
但是使用这个简单的代码,交叉点发生在 TotalSeconds = 7.3960529 (使用一个核心)
现在我仍然不开心,所以我试图通过并行打破这个来提高性能,一旦我完成,我会发布它
答案 2 :(得分:1)
Yorye Nathan用最后一个“不安全代码”方法给了我两个数组的最快交集。不幸的是,它对我来说仍然太慢,我需要组合阵列交叉点,最多可达2 ^ 32种组合,几乎没有?我做了以下修改和调整,时间缩短到2.6倍。您需要先进行一些预优化,确保您可以采取某种方式。我只使用索引而不是实际的对象或id或其他一些抽象的比较。所以,例如,如果你必须像这样交叉大数
Arr1: 103344,234566,789900,1947890, Arr2: 150034,234566,845465,23849854
将所有内容放入数组
Arr1: 103344,234566,789900,1947890,150034,845465,23849854
并为交集使用结果数组的有序索引
Arr1Index: 0,1,2,3 Arr2Index: 1,4,5,6
现在我们有更小的数字,我们可以与他们建立一些其他好的数组。从Yorye获取方法后我做了什么,我把Arr2Index扩展为理论上的布尔数组,实际上是字节数组,因为内存大小的含义,对于以下内容:
Arr2IndexCheck:0,1,0,0,1,1,1
这或多或少是一个字典,告诉我任何索引,如果第二个数组包含它。 下一步我没有使用内存分配也需要时间,而是在调用方法之前预先创建了结果数组,所以在查找我的组合的过程中,我从不实例化任何东西。当然你必须分别处理这个数组的长度,所以也许你需要将它存储在某个地方。
最后代码如下:
public static unsafe int IntersectSorted2(int[] arr1, byte[] arr2Check, int[] result)
{
int length;
fixed (int* pArr1 = arr1, pResult = result)
fixed (byte* pArr2Check = arr2Check)
{
int* maxArr1Adr = pArr1 + arr1.Length;
int* arr1Value = pArr1;
int* resultValue = pResult;
while (arr1Value < maxArr1Adr)
{
if (*(pArr2Check + *arr1Value) == 1)
{
*resultValue = *arr1Value;
resultValue++;
}
arr1Value++;
}
length = (int)(resultValue - pResult);
}
return length;
}
您可以看到函数返回结果数组大小,然后按照您的意愿执行(调整大小,保留它)。显然,结果数组必须至少具有arr1和arr2的最小大小。
最大的改进是,我只迭代第一个数组,最好是比第二个数组更小,所以你的迭代次数更少。较少的迭代意味着更少的CPU周期吗?
所以这里是两个有序数组的真正快速交集,如果你需要一个可靠的高性能;)。
答案 3 :(得分:0)
整数数组的arrCollection1
和arrCollection2
集合?在这种情况下,通过从i+1
开始第二次循环而不是0
答案 4 :(得分:0)
C#不支持SIMD。另外,我还没有弄清楚为什么,使用SSE的DLL在从C#调用时比非SSE等效函数更快。另外,我所知道的所有SIMD扩展都不支持分支,即你的“if”语句。
如果您使用的是.net 4.0,如果您有多个内核,则可以使用Parallel For来提高速度。否则,如果您的.net 3.5或更低版本,则可以编写多线程版本。
以下是与您类似的方法:
IList<int> intersect(int[] arr1, int[] arr2)
{
IList<int> intersect = new List<int>();
int i = 0, j = 0;
int iMax = arr1.Length - 1, jMax = arr2.Length - 1;
while (i < iMax && j < jMax)
{
while (i < iMax && arr1[i] < arr2[j]) i++;
if (arr1[i] == arr2[j]) intersect.Add(arr1[i]);
while (i < iMax && arr1[i] == arr2[j]) i++; //prevent reduntant entries
while (j < jMax && arr2[j] < arr1[i]) j++;
if (arr1[i] == arr2[j]) intersect.Add(arr1[i]);
while (j < jMax && arr2[j] == arr1[i]) j++; //prevent redundant entries
}
return intersect;
}
这个也可以防止任何条目出现两次。有两个大小为1000万的排序数组,它在大约一秒钟内完成。如果在For语句中使用array.Length,编译器应该删除数组边界检查,但我不知道它是否在while语句中有效。