[英]Efficiently convert byte array to Decimal
如果我有一個字節數組,並且想要將該數組的連續的16字節塊(包含.net的Decimal
表示形式)轉換為適當的Decimal
結構,最有效的方法是什么?
在我正在優化的情況下,這是我的探查器中顯示為最大CPU使用者的代碼。
public static decimal ByteArrayToDecimal(byte[] src, int offset)
{
using (MemoryStream stream = new MemoryStream(src))
{
stream.Position = offset;
using (BinaryReader reader = new BinaryReader(stream))
return reader.ReadDecimal();
}
}
為了擺脫MemoryStream
和BinaryReader
,我認為將BitConverter.ToInt32(src, offset + x)
s數組輸入Decimal(Int32[])
構造函數將比我在下面提供的解決方案要快,但是下面的版本是:奇怪的是,速度快一倍。
const byte DecimalSignBit = 128;
public static decimal ByteArrayToDecimal(byte[] src, int offset)
{
return new decimal(
BitConverter.ToInt32(src, offset),
BitConverter.ToInt32(src, offset + 4),
BitConverter.ToInt32(src, offset + 8),
src[offset + 15] == DecimalSignBit,
src[offset + 14]);
}
這是MemoryStream/BinaryReader
組合速度的10倍 ,我用一堆極限值對其進行了測試以確保它可以正常工作,但是十進制表示並不像其他原始類型那樣簡單,因此我還沒有確信它適用於100%的可能十進制值。
但是,從理論上講,有一種方法可以將這16個連續字節復制到內存中的其他位置,並聲明它為Decimal,而無需進行任何檢查。 有人知道這樣做的方法嗎?
(只有一個問題:盡管小數表示為16個字節,但是某些可能的值不能構成有效的小數,因此,執行未經檢查的memcpy
可能會破壞內容...)
還是還有其他更快的方法?
從流中讀取@Eugene Beresovksy非常昂貴。 MemoryStream當然是功能強大且用途廣泛的工具,但是直接讀取二進制數組的成本卻很高。 也許正因為如此,第二種方法的性能更好。
我為您提供了第三個解決方案,但是在我編寫它之前,有必要說我還沒有測試它的性能。
public static decimal ByteArrayToDecimal(byte[] src, int offset)
{
var i1 = BitConverter.ToInt32(src, offset);
var i2 = BitConverter.ToInt32(src, offset + 4);
var i3 = BitConverter.ToInt32(src, offset + 8);
var i4 = BitConverter.ToInt32(src, offset + 12);
return new decimal(new int[] { i1, i2, i3, i4 });
}
這是一種使構建基於二進制文件而不用擔心System.Decimal
規范的方法。 它是默認.net位提取方法的逆過程:
System.Int32[] bits = Decimal.GetBits((decimal)10);
編輯:
此解決方案可能不會表現更好,但也不會出現此問題: "(There's only one problem: Although decimals are represented as 16 bytes, some of the possible values do not constitute valid decimals, so doing an uncheckedmemcpy could potentially break things...)"
。
盡管這是一個古老的問題,但我還是很感興趣,因此決定進行一些實驗。 讓我們從實驗代碼開始。
static void Main(string[] args)
{
byte[] serialized = new byte[16 * 10000000];
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000000; ++i)
{
decimal d = i;
// Serialize
using (var ms = new MemoryStream(serialized))
{
ms.Position = (i * 16);
using (var bw = new BinaryWriter(ms))
{
bw.Write(d);
}
}
}
var ser = sw.Elapsed.TotalSeconds;
sw = Stopwatch.StartNew();
decimal total = 0;
for (int i = 0; i < 10000000; ++i)
{
// Deserialize
using (var ms = new MemoryStream(serialized))
{
ms.Position = (i * 16);
using (var br = new BinaryReader(ms))
{
total += br.ReadDecimal();
}
}
}
var dser = sw.Elapsed.TotalSeconds;
Console.WriteLine("Time: {0:0.00}s serialization, {1:0.00}s deserialization", ser, dser);
Console.ReadLine();
}
結果: Time: 1.68s serialization, 1.81s deserialization
。 這是我們的基准。 我還嘗試了Buffer.BlockCopy
到int[4]
,這給了我們0.42s的反序列化時間。 使用問題中描述的方法,反序列化可降至0.29s。
但是,從理論上講,有一種方法可以將這16個連續字節復制到內存中的其他位置,並聲明它為Decimal,而無需進行任何檢查。 有人知道這樣做的方法嗎?
嗯,是的,最快的方法是使用不安全的代碼,這在這里還可以,因為小數是值類型:
static unsafe void Main(string[] args)
{
byte[] serialized = new byte[16 * 10000000];
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000000; ++i)
{
decimal d = i;
fixed (byte* sp = serialized)
{
*(decimal*)(sp + i * 16) = d;
}
}
var ser = sw.Elapsed.TotalSeconds;
sw = Stopwatch.StartNew();
decimal total = 0;
for (int i = 0; i < 10000000; ++i)
{
// Deserialize
decimal d;
fixed (byte* sp = serialized)
{
d = *(decimal*)(sp + i * 16);
}
total += d;
}
var dser = sw.Elapsed.TotalSeconds;
Console.WriteLine("Time: {0:0.00}s serialization, {1:0.00}s deserialization", ser, dser);
Console.ReadLine();
}
此時,我們的結果是: Time: 0.07s serialization, 0.16s deserialization
。 可以肯定的是,這將是最快的速度...仍然,您必須在這里接受不安全的信息,並且我假設內容的編寫方式與讀取的方式相同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.