我有2个int数组。
int[] data1 #
int[] data2 #
我想创建第3个int [] data3,这是其他2个数组之间的差异。
让我们取data1中的第一个值。
值为15(例如)。
现在让我们在data2中获取第一个值。
值为3(例如)。
data3中的第一个值为12。
但是,如果第一个值是相反的,即
data1[0] = 3
data2[0] = 15
那么差异将是-12。但我希望它只有12岁。
目前我有一个for循环,我在那里进行计算,得到那种结果。
由于
N.B。 回应'闭门器'。我同意你的观点。我需要在这个问题上添加:
我正在寻找最有效(最快的方式,但低内存是第二优先)来促进这一点。使用Linq(据我所知)可能是最慢的方法吗?
答案 0 :(得分:8)
您正在寻找Zip
方法
var data3 = data1.Zip(data2, (d1,d2) => Math.Abs(d1 - d2)).ToArray();
Enumerable.Zip<TFirst, TSecond, TResult> Method
将指定的函数应用于两个序列的相应元素,生成一系列结果。
所以它只需要每个对应的元素,例如data1[0]
和data2[0]
,然后是data1[1]
和data2[1]
等......然后应用函数Math.Abs(d1-d2)
简单地减去两个数字并获得结果的绝对值。然后返回一个包含每个操作结果的序列。
答案 1 :(得分:5)
&#34;有没有办法在不通过循环枚举的情况下执行data1-data2 = data3?&#34;不,这在技术上是不可能的。
充其量,或者更糟糕的是,你可以调用为你做枚举的函数。但它会很慢。在LINQ的情况下,不合时宜的。
对于我正在处理其他答案的结果的机器,对于4KB表(1024个整数),结果如下。
循环代码:
for (int i = 0; i < data3.Length; i++)
{
data3[i] = Math.Abs(data1[i] - data2[i]);
}
这样的基本内存操作可以直接转换为机器代码,而不会出现可怕的性能和LINQ的大量内存占用。
故事的道德是:LINQ的可读性(在这种情况下是有争议的)不是为了表现(在这种情况下是明显的)。
UPDATE:?:由于生成的机器代码的差异而比If-Else快...至少只是比较两个值。它的机器代码远不如If-Else(它看起来不像你要写的那样#34;副手#34;)。显然,它不仅仅是编写If-Else语句的不同形式,而是针对简单条件赋值优化的完全独立命令。
结果代码比使用Math.Abs()的简单循环快大约8倍;请记住,您只能将循环展开为数据集大小的除数。你写道你的数据集大小是25920,所以8很好。 (最大值是64,但我怀疑它会有多高的意义)。我建议将这段代码隐藏在某些函数中,因为它很难看。
int[] data3 = new int[data1.Length];
for (int i = 0; i < data1.Length; i += 8)
{
int b;
b = (data1[i + 0] - data2[i + 0]);
data3[i + 0] = b < 0 ? -b : b;
b = (data1[i + 1] - data2[i + 1]);
data3[i + 1] = b < 0 ? -b : b;
b = (data1[i + 2] - data2[i + 2]);
data3[i + 2] = b < 0 ? -b : b;
b = (data1[i + 3] - data2[i + 3]);
data3[i + 3] = b < 0 ? -b : b;
b = (data1[i + 3] - data2[i + 4]);
data3[i + 4] = b < 0 ? -b : b;
b = (data1[i + 5] - data2[i + 5]);
data3[i + 5] = b < 0 ? -b : b;
b = (data1[i + 6] - data2[i + 6]);
data3[i + 6] = b < 0 ? -b : b;
b = (data1[i + 7] - data2[i + 7]);
data3[i + 7] = b < 0 ? -b : b;
}
这甚至不是最终形式。我会尝试在它上面做一些异端技巧。
正如我所提到的,仍有改进的地方。
在切断LINQ之后,主要的蜱munchkin是Abs()。当它从代码中删除时,我们在IF-ELSE和速记?:运算符之间留下了较量。 两者都是分支运算符,过去曾被广泛认为比线性代码慢。目前,易用性/写作倾向于挑选性能(有时正确,有时不正确)。
因此,让我们的分支条件成为线性的。有可能通过滥用这个代码中的分支包含仅对单个变量进行操作的事实。因此,让代码等同于this。
现在你还记得如何否定两个补码?,否定所有位并加一个。让我们无条件地在一行中做到这一点!
按位操作员有时间闪耀。 OR和AND很无聊,真正的男人使用异或。 XOR真的很酷吗?除了通常的行为之外,您还可以将其转换为NOT(否定)和NOP(无操作)。
1 XOR 1 = 0
0 XOR 1 = 1
因此,仅按1和1的值进行XOR操作会使您无法操作。
1 XOR 0 = 1
0 XOR 0 = 0
因此,只有0&#39;的XOR值根本没有任何作用。
我们可以从我们的号码获取标志。对于32位整数,它就像x>>31
一样简单。它将位符号移动到最低位。正如wiki会告诉你的那样,从左边插入的位将是零,因此x>>31
的结果对于负数(x <0)将为1,对于非负数(x&gt; = 0)将为0,对吗?
不。对于有符号值,Arithmetic shift用于普通位移。因此我们将根据符号获得-1或0 ....这意味着&#39; x&gt;&gt; 31&#39;将给出111 ... 111的负数和000 ... 000的非负数。如果您将根据此类移位的结果对原始x进行异或,则根据值符号执行NOT或NOP。另一个有用的东西是0将导致NOP用于加/否,所以我们可以根据值符号加/减-1。
所以&#39; x ^(x&gt;&gt; 31)&#39;将翻转负数位而不对非负数和&#39; x-(x>&gt; 31)&#39;将负x加1(否定负值给出正数)并且不对非负值进行更改。
合并后,你得到&#39;(x ^(x&gt;&gt; 31)) - (x&gt;&gt; 31)&#39; ...可以翻译成:
IF X<0
X=!X+1
它只是
IF X<0
X=-X
它如何影响性能? 我们的XorAbs()只需要四个基本的整数运算,一个加载和一个存储。分支运算符本身需要大约CPU额定值。虽然现代CPU非常擅长进行分支预测,但在提供顺序代码时,它们仍然可以更快地完成它。
分数是什么?
结果代码:
for (int i = 0; i < data1.Length; i++)
{
int x = data1[i] - data2[i];
data3[i] = (x ^ (x >> 31)) - (x >> 31);
}
CPU具有超高速缓存内存,当按顺序处理数组时,它会将整个数据块复制到缓存中。 但是如果你编写糟糕的代码,你会得到缓存未命中。您可以通过screwing up order of nested loops轻松陷入此陷阱。
并行(多线程,相同数据)必须在顺序块上工作才能充分利用cpu缓存。
手动编写线程允许您手动为线程选择块,但这很麻烦。 由于4.0 .NET附带了帮助程序,但是默认Parallel.For会导致缓存混乱。 因此,由于cache-miss,此代码实际上比单线程版本慢。
Parallel.For(0, data1.Length,
fn =>
{
int x = data1[fn] - data2[fn];
data3[fn] = (x ^ (x >> 31)) - (x >> 31);
}
可以通过在其中执行顺序操作来手动使用缓存数据。例如,你可以展开循环,但它的脏黑客和展开有自己的性能问题(它取决于CPU模型)。
Parallel.For(0, data1.Length >> 3,
i =>
{
int b;
b = (data1[i + 0] - data2[i + 0]);
data3[i + 0] = b < 0 ? (b ^ -1) + b : b;
b = (data1[i + 1] - data2[i + 1]);
data3[i + 1] = b < 0 ? (b ^ -1) + b : b;
b = (data1[i + 2] - data2[i + 2]);
data3[i + 2] = b < 0 ? (b ^ -1) + b : b;
b = (data1[i + 3] - data2[i + 3]);
data3[i + 3] = b < 0 ? (b ^ -1) + b : b;
b = (data1[i + 3] - data2[i + 4]);
data3[i + 4] = b < 0 ? (b ^ -1) + b : b;
b = (data1[i + 5] - data2[i + 5]);
data3[i + 5] = b < 0 ? (b ^ -1) + b : b;
b = (data1[i + 6] - data2[i + 6]);
data3[i + 6] = b < 0 ? (b ^ -1) + b : b;
b = (data1[i + 7] - data2[i + 7]);
data3[i + 7] = b < 0 ? (b ^ -1) + b : b;
}
然而,.NET也有Parrarel.ForEach和Load Balancing Partitioners。 通过使用它们,你可以获得最好的世界:
所以最终的代码是:
var rangePartitioner = Partitioner.Create(0, data1.Length);
Parallel.ForEach(rangePartitioner, (range, loopState)
=>
{
for (int i = range.Item1; i < range.Item2; i++)
{
int x = data1[i] - data2[i];
data3[i] = (x ^ (x >> 31)) - (x >> 31);
}
});
远远没有最大的CPU使用率(这比仅仅最大化其时钟,有多个缓存级别,多个管道等等更复杂)但它是可读的,快速的和平台无关的(除了整数大小,但是C#int是System.Int32的别名所以我们在这里很安全。)
在这里,我认为我们将停止优化。 它是作为一篇文章而不是回答而出现的,我希望没有人会为此清除我。
答案 2 :(得分:3)
这是另一种(不太可读但可能更高效)的方法,它不需要LINQ:
public static int[] GetDifference(int[] first, int[] second)
{
int commonLength = Math.Min(first.Length, second.Length);
int[] diff = new int[commonLength];
for (int i = 0; i < commonLength; i++)
diff[i] = Math.Abs(first[i] - second[i]);
return diff;
}
为什么效率更高?因为ToArray
has to resize the array直到它知道最终尺寸。
答案 3 :(得分:1)
var data3 = data1.Select((x,i)=>new {x,i})
.Join
(
data2.Select((x,i)=>new {x,i}),
x=>x.i,
x=>x.i,
(d1,d2)=>Math.Abs(d1.x-d2.x)
)
.ToArray();