关于C#:比较2字节数组

Compare 2 byte arrays

我有两个int数组。

1
2
int[] data1 #
int[] data2 #

我想创建第三个int[]data3,它是其他两个数组之间的差异。

让我们取数据1中的第一个值。

值为15(例如)。

现在让我们取数据2中的第一个值。

值为3(例如)。

数据3中的第一个值为12。

但是,如果第一个值是相反的,即

1
2
data1[0]  = 3
data2[0]  = 15

那么差异将是-12。但我希望只有12岁。

现在我有了一个for循环,我在那里做计算,以得到这种类型的结果。

  • 有没有一种方法可以在不通过枚举的情况下执行data1-data2=data3一个循环?
  • 如果是这样,我可以不使用减号就得到差额吗?数字?
  • 谢谢

    N.B.作为对"闭门人"的回应。在某种程度上我同意你的观点。我需要补充的是:

    我正在寻找最有效的(最快的方式,但低记忆是第二优先)来促进这一点。使用LINQ(据我所知)可能是最慢的方法吗?


    你在找Zip方法

    1
    var data3 = data1.Zip(data2, (d1,d2) => Math.Abs(d1 - d2)).ToArray();

    Enumerable.Zip Method

    Applies a specified function to the corresponding elements of two sequences, producing a sequence of the results.

    所以它简单地取每个对应的元素,比如data1[0]data2[0],然后是data1[1]data2[1]等等。然后应用函数Math.Abs(d1-d2),它简单地减去两个数字,得到结果的绝对值。然后返回一个序列,该序列包含每个操作的结果。


    "有一个办法是- 2数据的数据没有通过环enumerating =?"在理论上,它是不可能的。。。。。。。。。。。。。。

    在最好的,不是最或是呼叫功能,你可以将你的枚举。但这将是缓慢的。LINQ的情况,ungodly慢。

    我目前的工作机器的答案是来自其他在线跟踪表(1024 integers)为4KB。

    • 23560 - paraskevopoulos Giannis蜱。数组的数组转换不可太快,tolist(路径)复制(阵列)是一.toarray约25倍,比慢的链array.copy()。
    • 10198 - selman22蜱。2倍的速度更快,但仍然缓慢。从创建的事件是眼睛糖果,让prettier也更快。一些匿名的方法端跟踪你周围铺设,这需要更多的CPU时间是可以吃的呼叫返回操作(记住,这比我们在数学中的几个周期的CPU就可以)。
    • 提姆schmelter getdifference蜱(566)的功能(主要是在culprit JIT代码,在本地和/或更经常的使用差分将是negligible)
    • 蜱只是环27。超过400倍更快的速度比的拉链,在800和反向转换阵列列表。

    循环码

    1
    2
    3
    4
    for (int i = 0; i < data3.Length; i++)
    {
      data3[i] = Math.Abs(data1[i] - data2[i]);
    }

    这样的操作可以直接翻译的基本内存的机器代码的性能和内存占用没有可怕的humongous of LINQ的。

    道德的故事:LINQ是一readability(在这一案例是一个不arguable)表演(在这案例是一个noticeable)。

    优化时间!让我们稍微滥用一下CPU。

  • 展开循环。或者不要。你的经验可能会有所不同。即使在汇编程序本身循环展开性能增益或损失变化在同一系列处理器中。新的CPU和编译器意识到旧的技巧并简单地自己实现它们。为了I3-3220我在循环上测试了代码,展开到4行,结果更快在32位代码上执行,但在64位代码上执行慢一点,而展开到8则相反。
  • 为x64编译。因为我们在这里处理32位数据,所以我们不会使用64位寄存器…还是我们?在x86上少于一半寄存器确实可用于生成的代码(以汇编形式编写手上你可以挤出更多),但在X64上你可以得到八个免费使用的奖励寄存器。不访问内存所做的越多,代码就越快。在这种情况下,速度增益约为20%。
  • 关闭Visual Studio。不要在32位IDE中加速测试64位代码(目前没有64位版本,可能不会用于长时间)。它将使X64代码的速度大约慢两倍,因为到架构不匹配。(嗯……无论如何,您不应该在调试器下加速测试代码…)
  • 不要过度使用内置函数。在这种情况下,数学.abs头顶隐藏在里面。由于某些原因(需要对IL进行分析才能发现),检查负值的速度更快?:如果不是的话。这样的支票节省了很多时间。
  • 更新:?:比if-else更快,因为结果机器代码不同…至少比较两个值。它的机器代码远没有其他代码那么奇怪(这看起来不像你"用手"写的东西)。显然,它不仅是编写if-else语句的不同形式,而且是为简单条件赋值而优化的完全独立的命令。好的。

    生成的代码比使用math.abs()的简单循环快大约8倍;记住,您只能将循环展开到数据集大小的除数。您写的数据集大小是25920,所以8可以。(最大值是64,但我怀疑它有任何意义去这么高)。我建议将这段代码隐藏在某个函数中,因为它很简单。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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后,主要蜱类为Abs()。当它从代码中删除后,我们就剩下了if-else和shorthand之间的竞争了?接线员。两者都是分支运算符,过去人们普遍认为分支运算符比线性代码慢。目前,易用性/易写性往往会超过性能(有时正确,有时错误)。好的。

    所以我们把分支条件设为线性。这可能是因为滥用了这样一个事实:代码中的分支只包含对单个变量进行运算的数学。所以让我们让代码等价于这个。好的。

    现在你还记得怎么消去二的补数吗?,对所有位求反并加一。那就让我们在没有条件的情况下用一行来做吧!好的。

    现在是位运算符发光的时候了。或者,而且是无聊的,真正的男人使用XOR。XOR有什么好酷的?除了它通常的行为,你也可以把它变成非(否定)和非(非操作)。好的。

    1
    2
    1 XOR 1 = 0
    0 XOR 1 = 1

    因此,仅用1填充的值xor'ing不会为您提供操作。好的。

    1
    2
    1 XOR 0 = 1
    0 XOR 0 = 0

    因此,仅用0填充的值xor'ing根本不起作用。好的。

    我们可以从我们的号码中获得符号。对于32位整数,它与x>>31一样简单。它将位符号移动到最低位。正如wiki告诉你的那样,从左边插入的位将是零,所以x>>31的结果将是1表示负数(x<0),0表示非负数(x>=0),对吗?好的。

    不。对于有符号值,算术移位用于普通位移位。所以我们会得到-1或0,这取决于符号……这意味着"x>>31"将给出111…111表示负,000…000表示非负。如果您将通过这种移位对原始X执行异或,则将根据值符号执行not或nop。另一个有用的事情是0将导致nop用于加/减,因此我们可以根据值符号加/减-1。好的。

    因此,"x^(x>>31)"将在不更改非负值的情况下翻转负数的位,"x-(x>>31)"将向负x添加1(负值表示正值),而不更改非负值。好的。

    合并后,您得到'(x^(x>>31))—(x>>31)'…可译为:好的。

    1
    2
    IF X<0
      X=!X+1

    它只是好的。

    1
    2
    IF X<0
      X=-X

    它如何影响性能?我们的xorabs()只需要四个基本的整数操作,一个加载和一个存储。分支运算符本身占用的CPU信号量差不多。虽然现代的CPU擅长做分支预测,但是在输入顺序代码时不做分支预测仍然会更快。好的。

    比分是多少?好的。

  • 大约比内置abs()快四倍;
  • 大约是以前代码的两倍(没有展开的版本)
  • 根据CPU的不同,它可以在不展开循环的情况下获得更好的结果。由于消除了代码分支,CPU可以在其上"展开"循环自己的。(哈斯韦尔展开时很奇怪)
  • 生成代码:好的。

    1
    2
    3
    4
    5
    for (int i = 0; i < data1.Length; i++)
    {
      int x = data1[i] - data2[i];
      data3[i] = (x ^ (x >> 31)) - (x >> 31);
    }

    并行性和缓存使用

    CPU拥有超高速缓存内存,当按顺序处理一个数组时,它会将整个数组块复制到缓存中。但是,如果编写糟糕的代码,则会导致缓存未命中。通过扭曲嵌套循环的顺序,您很容易落入这个陷阱。好的。

    并行性(多个线程,相同的数据)必须在连续块上工作,以便充分利用CPU缓存。好的。

    手工编写线程将允许您手动为线程选择块,但这是一种麻烦的方式。因为4.0.net提供了相应的帮助程序,但是默认的parallel.for会造成缓存混乱。因此,由于缓存未命中,此代码实际上比其单线程版本慢。好的。

    1
    2
    3
    4
    5
    6
    Parallel.For(0, data1.Length,
    fn =>
    {
      int x = data1[fn] - data2[fn];
      data3[fn] = (x ^ (x >> 31)) - (x >> 31);
    }

    通过在缓存数据中执行顺序操作,可以手动使用缓存数据。例如,您可以展开循环,但它的脏黑客和展开有自己的性能问题(这取决于CPU模型)。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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和负载平衡分区。通过使用这两种方法,您可以获得世界上最好的:好的。

    • 数据集大小无关代码
    • 简短、整洁的代码
    • 多线程
    • 良好的缓存使用率

    所以最终的代码是:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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的别名,因此我们在这里是安全的)。好的。

    在这里,我认为我们将停止优化。这是一篇文章,而不是一个答案,我希望没有人会因为它而净化我。好的。好啊。


    下面是另一种不需要LINQ的方法(不太可读,但可能更高效一点):

    1
    2
    3
    4
    5
    6
    7
    8
    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必须调整数组的大小,直到它知道最终的大小。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    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();