现有 10000 个 double 数值以二进制编码的形式存储在 Span<byte> 中。怎样才能将其转换成 double[] (或者 Span<double>) 呢?

最傻瓜的方案

当然是循环使用 BitConverter 类的方法逐个转换。代码如下:

1
2
3
4
5
6
7
8
9
10
private double[] ToDoubleArray(Span<byte> bytes)
{
int size = sizeof(double);
var doubles = new double[bytes.Length / size];
for (int i = 0; i < doubles.Length; i++)
{
doubles[i] = BitConverter.ToDouble(bytes.Slice(i * size, size));
}
return doubles;
}

当然,可想而知,这种方法的性能非常一般。

无视类型复制

C/C++ 中的 memcopy() 等函数为我们提供了直接复制内存区域内容,而无视其类型属性的能力。而 .NET 中的 Buffer 类为我们提供了相似的方法。其中一个方法是 Buffer.BlockCopy().

如果源数据与程序运行平台的大小端属性一致的话,使用这一方法就可以快速地实现转换。

1
2
3
4
5
6
7
8
9
10
11
12
private double[] ToDoubleArray(Span<byte> bytes)
{
var doubles = new double[bytes.Length / sizeof(double)];
Buffer.BlockCopy(
src: bytes.ToArray(),
srcOffset: 0,
dst: doubles,
dstOffset: 0,
count: bytes.Length
);
return doubles;
}

在本人测试中,这种方案与逐个转换的方案相比,可以带来 90% 以上的性能提升。不幸的是该方法目前只有接受数组的版本,因此在与 Span<byte> 配合使用时需要调用 ToArray() 方法。这将带来额外的内存开销。

不安全的复制

如果实际情况允许使用 unsafe 代码块,则可以使用 Buffer.MemoryCopy() 方法来省去将 Span<byte> 转换为数组的开销。需要注意的是,并非所有 .NET 语言均支持不安全代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private double[] ToDoubleArray(Span<byte> bytes)
{
var doubles = new double[bytes.Length / sizeof(double)];
unsafe
{
fixed (void* dest = doubles)
{
fixed (void* src = bytes)
{
Buffer.MemoryCopy(
source: src,
destination: dest,
destinationSizeInBytes: items.Length * sizeof(double),
sourceBytesToCopy: message.Message.Length
);
}
}
}
return doubles;
}

使用这一方法与使用 BlockCopy() 方法相比,在执行时间方面没有明显的差异。但相比可以降低内存开销。