鉴于您只存储位,您可以通过将位存储到 uint 值中来提高存储使用率,与为每个值使用 int 相比,这将减少 32 倍所需的空间量.
如果您这样做,那么您还可以使用多种不同的"Hamming Weight" 算法之一更有效地计算设置位的数量。
这种方法的缺点是使用数组BitMatrix 索引器访问单个位可能会更慢,但设置位数量的计算要快得多(RELEASE 模式建立在我的电脑)。
这是示例代码;重要的类是BitMatrix:
using System;
using System.Diagnostics;
namespace Demo
{
class Program
{
static void Main()
{
int[,] matrix = new int[1000, 1000];
BitMatrix bitMatrix = new BitMatrix(1000, 1000);
// Randomly populate matrices and calculate expected count.
var rng = new Random(985912);
int expected = 0;
for (int r = 0; r < 1000; ++r)
{
for (int c = 0; c < 1000; ++c)
{
if ((rng.Next() & 1) == 0)
continue;
++expected;
matrix[r, c] = 1;
bitMatrix[r, c] = true;
}
}
Console.WriteLine("Expected = " + expected);
// Time the explicit matrix loop.
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1000; ++i)
if (count1(matrix) != expected)
Console.WriteLine("count1() failed");
var elapsed1 = sw.ElapsedTicks;
Console.WriteLine(sw.Elapsed);
// Time the hamming weight approach.
sw.Restart();
for (int i = 0; i < 1000; ++i)
if (bitMatrix.NumSetBits() != expected)
Console.WriteLine("NumSetBits() failed");
var elapsed2 = sw.ElapsedTicks;
Console.WriteLine(sw.Elapsed);
Console.WriteLine("BitMatrix matrix is " + elapsed1 / elapsed2 + " times faster");
}
static int count1(int[,] matrix)
{
int h = 1 + matrix.GetUpperBound(0);
int w = 1 + matrix.GetUpperBound(1);
int c = 0;
for (int i = 0; i < h; ++i)
for (int j = 0; j < w; ++j)
if (matrix[i, j] == 1)
++c;
return c;
}
}
public sealed class BitMatrix
{
public BitMatrix(int rows, int cols)
{
Rows = rows;
Cols = cols;
bits = new uint[(rows*cols+31)/32];
}
public int Rows { get; }
public int Cols { get; }
public int NumSetBits()
{
int count = 0;
foreach (uint i in bits)
count += hammingWeight(i);
return count;
}
public bool this[int row, int col]
{
get
{
int n = row * Cols + col;
int i = n / 32;
int j = n % 32;
uint m = 1u << j;
return (bits[i] & m) != 0;
}
set
{
int n = row * Cols + col;
int i = n / 32;
int j = n % 32;
uint m = 1u << j;
if (value)
bits[i] |= m;
else
bits[i] &= ~m;
}
}
static int hammingWeight(uint i)
{
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (int)((((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);
}
readonly uint[] bits;
}
}
如果您正在运行 64 位代码,那么使用 ulong 的数组并计算 64 位汉明权重实际上效率更高。
当我在我的 PC 上尝试时,它的速度提高了 120 倍以上。
这是BitMatrix 的 64 位版本:
public sealed class BitMatrix
{
public BitMatrix(int rows, int cols)
{
Rows = rows;
Cols = cols;
bits = new ulong[(rows*cols+63)/64];
}
public int Rows { get; }
public int Cols { get; }
public int NumSetBits()
{
int count = 0;
foreach (ulong i in bits)
count += hammingWeight(i);
return count;
}
public bool this[int row, int col]
{
get
{
int n = row * Cols + col;
int i = n / 64;
int j = n % 64;
ulong m = 1ul << j;
return (bits[i] & m) != 0;
}
set
{
int n = row * Cols + col;
int i = n / 64;
int j = n % 64;
ulong m = 1ul << j;
if (value)
bits[i] |= m;
else
bits[i] &= ~m;
}
}
static int hammingWeight(ulong i)
{
i = i - ((i >> 1) & 0x5555555555555555UL);
i = (i & 0x3333333333333333UL) + ((i >> 2) & 0x3333333333333333UL);
return (int)(unchecked(((i + (i >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56);
}
readonly ulong[] bits;
}
观察:在NumSetBits() 的循环中使用for() 而不是foreach 会稍微快一些,例如:
public int NumSetBits()
{
int count = 0;
for (var index = 0; index < bits.Length; index++)
count += hammingWeight(bits[index]);
return count;
}
在我的 PC 上,这会将性能从 120 倍提高到 130 倍。
最后:如果你想利用多线程,你可以这样做(注意使用Partitioner - 这是为了增加每个线程计算的数据块大小,使其更高效):
public int NumSetBits()
{
int count = 0;
var partitioner = Partitioner.Create(0, bits.Length);
Parallel.ForEach(partitioner, (range, loopState) =>
{
int subtotal = 0;
for (int i = range.Item1; i < range.Item2; ++i)
{
subtotal += hammingWeight(bits[i]);
}
Interlocked.Add(ref count, subtotal);
});
return count;
}
有了这个变化,汉明方法快了近 200 倍(对于 2000x2000 矩阵来说快了近 300 倍),但请注意,它的速度快多少取决于设置的 1 位的比例。