Самый быстрый способ вычисления суммы битов в массиве байтов

У меня есть два байтовых массива одинаковой длины. Мне нужно выполнить операцию XOR между каждым байтом и после этого вычислить сумму битов.

например:

11110000^01010101 = 10100101 -> so 1+1+1+1 = 4

Мне нужно сделать ту же операцию для каждого элемента в массив байтов.

9 ответов


используйте таблицу подстановки. После XORing есть только 256 возможных значений, поэтому это не займет много времени. В отличие от решения izb, я бы не предложил вручную вводить все значения, хотя - вычислить таблицу поиска после при запуске, используя один из ответов цикла.

например:

public static class ByteArrayHelpers
{
    private static readonly int[] LookupTable =
        Enumerable.Range(0, 256).Select(CountBits).ToArray();

    private static int CountBits(int value)
    {
        int count = 0;
        for (int i=0; i < 8; i++)
        {
           count += (value >> i) & 1;
        }
        return count;
    }

    public static int CountBitsAfterXor(byte[] array)
    {
        int xor = 0;
        foreach (byte b in array)
        {
            xor ^= b;
        }
        return LookupTable[xor];
    }
}

(вы мог бы сделайте это методом расширения, если вы действительно хотите...)

обратите внимание на использование byte[] на CountBitsAfterXor способ - вы мог бы сделать IEnumerable<byte> для большей общности, но итерация по массиву (который, как известно, является массивом во время компиляции) будет быстрее. Вероятно, только микроскопически быстрее, но эй, вы попросили быстрый путь :)

Я бы почти наверняка на самом деле это как

public static int CountBitsAfterXor(IEnumerable<byte> data)

в реальной жизни, но увидеть, что работает лучше для вас.

Также обратите внимание на тип the xor переменная как int. Фактически, нет оператора XOR, определенного для byte значения, и если вы сделали xor a byte он все равно будет компилироваться из - за природы составных операторов присваивания, но он будет выполнять приведение на каждой итерации-по крайней мере, в IL. Вполне возможно, что JIT позаботится об этом, но нет необходимости даже просить его:)


самый быстрый способ, вероятно, будет 256-элементной таблицей поиска...

int[] lut
{
    /*0x00*/ 0,
    /*0x01*/ 1,
    /*0x02*/ 1,
    /*0x03*/ 2
    ...
    /*0xFE*/ 7,
    /*0xFF*/ 8
}

например

11110000^01010101 = 10100101 -> lut[165] == 4

это чаще называют подсчетом битов. Для этого существуют буквально десятки различных алгоритмов. здесь - Это один сайт, в котором перечислены несколько наиболее известных методов. Есть даже конкретные инструкции CPU для этого.

теоретически Microsoft может добавить BitArray.CountSetBits функция, которая получает JITed с лучшим алгоритмом для этой архитектуры процессора. Я, например, был бы рад такому добавлению.


Как я понял, вы хотите суммировать биты каждого XOR между левым и правым байтами.

for (int b = 0; b < left.Length; b++) {
  int num = left[b] ^ right[b];
  int sum = 0;

  for (int i = 0; i < 8; i++) {
    sum += (num >> i) & 1;
  }

   // do something with sum maybe?
}

Я не уверен, имеете ли вы в виду сумму байтов или битов. Чтобы суммировать биты в байте, это должно работать:

int nSum = 0;
for (int i=0; i<=7; i++)
{
   nSum += (byte_val>>i) & 1;
}

тогда вам понадобится xoring и массив, который, конечно же, будет вокруг этого.


следует сделать

int BitXorAndSum(byte[] left, byte[] right) {
  int sum = 0;
  for ( var i = 0; i < left.Length; i++) { 
    sum += SumBits((byte)(left[i] ^ right[i]));
  }
  return sum;
}

int SumBits(byte b) {
  var sum = 0;
  for (var i = 0; i < 8; i++) {
    sum += (0x1) & (b >> i);
  }
  return sum;
}

его можно переписать как ulong и использовать unsafe указатель, но byte - это легче понять:

static int BitCount(byte num)
{
    // 0x5 = 0101 (bit) 0x55 = 01010101
    // 0x3 = 0011 (bit) 0x33 = 00110011
    // 0xF = 1111 (bit) 0x0F = 00001111
    uint count = num;
    count = ((count >> 1) & 0x55) + (count & 0x55);
    count = ((count >> 2) & 0x33) + (count & 0x33);
    count = ((count >> 4) & 0xF0) + (count & 0x0F);
    return (int)count;
}

общая функция для подсчета битов может выглядеть так:

int Count1(byte[] a)
{
  int count = 0;
  for (int i = 0; i < a.Length; i++)
  {
    byte b = a[i];
    while (b != 0)
    {
      count++;
      b = (byte)((int)b & (int)(b - 1));
    }
  }
  return count;
}

чем меньше 1-бит, тем быстрее это работает. Он просто перебирает каждый байт и переключает самый низкий 1 бит этого байта, пока байт не станет 0. Отливки необходимы, чтобы компилятор перестал жаловаться на расширение и сужение типа.

ваша проблема может быть решена с помощью этого:
int Count1Xor(byte[] a1, byte[] a2)
{
  int count = 0;
  for (int i = 0; i < Math.Min(a1.Length, a2.Length); i++)
  {
    byte b = (byte)((int)a1[i] ^ (int)a2[i]);
    while (b != 0)
    {
      count++;
      b = (byte)((int)b & (int)(b - 1));
    }
  }
  return count;
}

таблица поиска должна быть самой быстрой, но если вы хотите сделать это без таблицы поиска, это будет работать для байтов всего за 10 операций.

public static int BitCount(byte value) {
    int v = value - ((value >> 1) & 0x55);
    v = (v & 0x33) + ((v >> 2) & 0x33);
    return ((v + (v >> 4) & 0x0F));
}

это байтовая версия общей функции подсчета битов, описанной в сайт Шона Эрона Андерсона.