C#處理類(lèi)型和二進(jìn)制數(shù)據(jù)轉(zhuǎn)換并提高程序性能
C# 原語(yǔ)類(lèi)型
按照內(nèi)存分配來(lái)區(qū)分,C# 有值類(lèi)型、引用類(lèi)型;
按照基礎(chǔ)類(lèi)型類(lèi)型來(lái)分,C# 有 內(nèi)置類(lèi)型、通用類(lèi)型、自定義類(lèi)型、匿名類(lèi)型、元組類(lèi)型、CTS類(lèi)型(通用類(lèi)型系統(tǒng));
C# 的基礎(chǔ)類(lèi)型包括:
- 整型: sbyte, byte, short, ushort, int, uint, long, ulong
- 實(shí)數(shù)類(lèi)型: float, double, decimal
- 字符類(lèi)型: char
- 布爾類(lèi)型: bool
- 字符串類(lèi)型: string
C# 中的原語(yǔ)類(lèi)型,是基礎(chǔ)類(lèi)型中的值類(lèi)型,不包括 string。原語(yǔ)類(lèi)型可以使用 sizeof()
來(lái)獲取字節(jié)大小,除 bool 外,都有 MaxValue
、MinValue
兩個(gè)字段。
sizeof(uint); uint.MaxValue uint.MinValue
我們也可以在泛型上進(jìn)行區(qū)分,上面的教程類(lèi)型,除了 string,其他類(lèi)型都是 struct。
<T>() where T : struct { }
1,利用 Buffer 優(yōu)化數(shù)組性能
Buffer 可以操作基元類(lèi)型(int、byte等)的數(shù)組,利用.NET 中的 Buffer 類(lèi),通過(guò)更快地訪問(wèn)內(nèi)存中的數(shù)據(jù)來(lái)提高應(yīng)用程序的性能。
Buffer 可以直接從基元類(lèi)型的數(shù)組中,直接取出指定數(shù)量的字節(jié),或者給其某個(gè)字節(jié)設(shè)置值。
Buffer 主要在直接操作內(nèi)存數(shù)據(jù)、操作非托管內(nèi)存時(shí),使用 Buffer 可以帶來(lái)安全且高性能的體驗(yàn)。
方法 | 說(shuō)明 |
---|---|
BlockCopy(Array, Int32, Array, Int32, Int32) | 將指定數(shù)目的字節(jié)從起始于特定偏移量的源數(shù)組復(fù)制到起始于特定偏移量的目標(biāo)數(shù)組。 |
ByteLength(Array) | 返回指定數(shù)組中的字節(jié)數(shù)。 |
GetByte(Array, Int32) | 檢索指定數(shù)組中指定位置的字節(jié)。 |
MemoryCopy(Void, Void, Int64, Int64) | 將指定為長(zhǎng)整型值的一些字節(jié)從內(nèi)存中的一個(gè)地址復(fù)制到另一個(gè)地址。此 API 不符合 CLS。 |
MemoryCopy(Void, Void, UInt64, UInt64) | 將指定為無(wú)符號(hào)長(zhǎng)整型值的一些字節(jié)從內(nèi)存中的一個(gè)地址復(fù)制到另一個(gè)地址。此 API 不符合 CLS。 |
SetByte(Array, Int32, Byte) | 將指定的值分配給指定數(shù)組中特定位置處的字節(jié)。 |
下面來(lái)介紹一下 Buffer 的一些使用方法。
BlockCopy 可以復(fù)制數(shù)組的一部分到另一個(gè)數(shù)組,其使用方法如下:
int[] arr1 = new int[] { 1, 2, 3, 4, 5 }; int[] arr2 = new int[10] { 0, 0, 0, 0, 0, 6, 7, 8, 9, 10 }; // int = 4 byte // index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ... ... // arr1: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00 // arr2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00 09 00 00 00 0A 00 00 00 // Buffer.ByteLength(arr1) == 20 , // Buffer.ByteLength(arr2) == 40 Buffer.BlockCopy(arr1, 0, arr2, 0, 19); for (int i = 0; i < arr2.Length; i++) { Console.Write(arr2[i] + ","); }
.SetByte()
則可細(xì)粒度地設(shè)置數(shù)組的值,即可以直接設(shè)置數(shù)組中任意一位的值,其使用方法如下:
//source data: // 0000,0001,0002,00003,0004 // 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 int[] a = new int[] { 0, 1, 2, 3, 4 }; foreach (var item in a) { Console.Write(item + ","); } Console.WriteLine("\n------\n"); // see : https://stackoverflow.com/questions/26455843/how-are-array-values-stored-in-little-endian-vs-big-endian-architecture // memory save that data: // 0000 1000 2000 3000 4000 for (int i = 0; i < Buffer.ByteLength(a); i++) { Console.Write(Buffer.GetByte(a, i)); if (i != 0 && (i + 1) % 4 == 0) Console.Write(" "); } // 16 進(jìn)制 // 0000 1000 2000 3000 4000 Console.WriteLine("\n------\n"); Buffer.SetByte(a, 0, 4); Buffer.SetByte(a, 4, 3); Buffer.SetByte(a, 8, 2); Buffer.SetByte(a, 12, 1); Buffer.SetByte(a, 16, 0); foreach (var item in a) { Console.Write(item + ","); } Console.WriteLine("\n------\n");
建議復(fù)制代碼自行測(cè)試,斷點(diǎn)調(diào)試,觀察過(guò)程。
2,BinaryPrimitives 細(xì)粒度操作字節(jié)數(shù)組
System.Buffers.Binary.BinaryPrimitives
用來(lái)以精確的方式讀取或者字節(jié)數(shù)組,只能對(duì) byte
或 byte
數(shù)組使用,其使用場(chǎng)景非常廣泛。
BinaryPrimitives 的實(shí)現(xiàn)原理是 BitConverter
,BinaryPrimitives 對(duì) BitConverter 做了一些封裝。BinaryPrimitives 的主要使用方式是以某種形式從 byte 或 byte 數(shù)組中讀取出信息。
例如,BinaryPrimitives 在 byte 數(shù)組中,一次性讀取四個(gè)字節(jié),其示例代碼如下:
// source data: 00 01 02 03 04 // binary data: 00000000 00000001 00000010 00000011 000001000 byte[] arr = new byte[] { 0, 1, 2, 3, 4, }; // read one int,4 byte int head = BinaryPrimitives.ReadInt32BigEndian(arr); // 5 byte: 00000000 00000001 00000010 00000011 000001000 // read 4 byte(int) : 00000000 00000001 00000010 00000011 // = 66051 Console.WriteLine(head);
在 BinaryPrimitives 中有大端小端之分。在 C# 中,應(yīng)該都是小端在前大端在后的,具體可能會(huì)因處理器架構(gòu)而不同。
你可以使用BitConverter.IsLittleEndian
來(lái)判斷在當(dāng)前處理器上,C# 程序是大端還是小端在前。
以 .Read...()
開(kāi)頭的方法,可以以字節(jié)為定位訪問(wèn) byte
數(shù)組上的數(shù)據(jù)。
以 .Write...()
開(kāi)頭的方法,可以向某個(gè)位置寫(xiě)入數(shù)據(jù)。
下面舉個(gè)例子:
// source data: 00 01 02 03 04 // binary data: 00000000 00000001 00000010 00000011 000001000 byte[] arr = new byte[] { 0, 1, 2, 3, 4, }; // read one int,4 byte // 5 byte: 00000000 00000001 00000010 00000011 000001000 // read 4 byte(int) : 00000000 00000001 00000010 00000011 // = 66051 int head = BinaryPrimitives.ReadInt32BigEndian(arr); Console.WriteLine(head); // BinaryPrimitives.WriteInt32LittleEndian(arr, 1); BinaryPrimitives.WriteInt32BigEndian(arr.AsSpan().Slice(0, 4), 0b00000000_00000000_00000000_00000001); // to : 00000000 00000000 00000000 00000001 | 000001000 // read 4 byte head = BinaryPrimitives.ReadInt32BigEndian(arr); Console.WriteLine(head);
建議復(fù)制代碼自行測(cè)試,斷點(diǎn)調(diào)試,觀察過(guò)程。
提高代碼安全性
C#和.NET Core 有的許多面向性能的 API,C# 和 .NET 的一大優(yōu)點(diǎn)是可以在不犧牲內(nèi)存安全性的情況下編寫(xiě)快速出高性能的庫(kù)。我們?cè)诒苊馐褂?unsafe 代碼的情況下,通過(guò)二進(jìn)制處理類(lèi),我們可以編寫(xiě)出高性能的代碼和具有安全性的代碼。
在 C# 中,我們有以下類(lèi)型可以高效操作字節(jié)/內(nèi)存:
Span
和C#類(lèi)型可以快速安全地訪問(wèn)內(nèi)存。表示任意內(nèi)存的連續(xù)區(qū)域。使用 span 使我們可以序列化為托管.NET數(shù)組,堆棧分配的數(shù)組或非托管內(nèi)存,而無(wú)需使用指針。.NET可以防止緩沖區(qū)溢出。ref struct
、Span
stackalloc
用于創(chuàng)建基于堆棧的數(shù)組。stackalloc
是在需要較小緩沖區(qū)時(shí)避免分配的有用工具。- 低級(jí)方法,并在原始類(lèi)型和字節(jié)之間直接轉(zhuǎn)換。
MemoryMarshal.GetReference()
、Unsafe.ReadUnaligned()
、Unsafe.WriteUnaligned()
BinaryPrimitives
具有用于在.NET基本類(lèi)型和字節(jié)之間進(jìn)行有效轉(zhuǎn)換的輔助方法。例如,讀取小尾數(shù)字節(jié)并返回?zé)o符號(hào)的64位數(shù)字。所提供的方法經(jīng)過(guò)了最優(yōu)化,并使用了向量化。BinaryPrimitives.ReadUInt64LittleEndian
、BinaryPrimitive
以 .Reverse...()
開(kāi)頭的方法,可以置換基元類(lèi)型的大小端。
short value = 0b00000000_00000001; // to endianness: 0b00000001_00000000 == 256 BinaryPrimitives.ReverseEndianness(0b00000000_00000000_00000000_00000001); Console.WriteLine(BinaryPrimitives.ReverseEndianness(value)); value = 0b00000001_00000000; Console.WriteLine(BinaryPrimitives.ReverseEndianness(value)); // 1
3,BitConverter、MemoryMarshal
BitConverter 可以基元類(lèi)型和 byte 相互轉(zhuǎn)換,例如 int 和 byte 互轉(zhuǎn),或者任意取出、寫(xiě)入基元類(lèi)型的任意一個(gè)字節(jié)。
其示例如下:
// 0b...1_00000100 int value = 260; // byte max value:255 // a = 0b00000100; 丟失 int ... 00000100 之前的位數(shù)。 byte a = (byte)value; // a = 4 Console.WriteLine(a); // LittleEndian // 0b 00000100 00000001 00000000 00000000 byte[] b = BitConverter.GetBytes(260); Console.WriteLine(Buffer.GetByte(b, 1)); // 4 if (BitConverter.IsLittleEndian) Console.WriteLine(BinaryPrimitives.ReadInt32LittleEndian(b)); else Console.WriteLine(BinaryPrimitives.ReadInt32BigEndian(b));
MemoryMarshal 提供與 Memory<T>
、ReadOnlyMemory<T>
、Span<T>
和 ReadOnlySpan<T>
進(jìn)行交互操作的方法。
MemoryMarshal
在 System.Runtime.InteropServices
命名空間中。
我們先介紹 MemoryMarshal.Cast()
,它可以將一種基元類(lèi)型的范圍強(qiáng)制轉(zhuǎn)換為另一種基元類(lèi)型的范圍。
// 1 int = 4 byte // int [] {1,2} // 0001 0002 var byteArray = new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }; Span<byte> byteSpan = byteArray.AsSpan(); // byte to int Span<int> intSpan = MemoryMarshal.Cast<byte, int>(byteSpan); foreach (var item in intSpan) { Console.Write(item + ","); }
最簡(jiǎn)單的說(shuō)法是,MemoryMarshal 可以將一種結(jié)構(gòu)轉(zhuǎn)換為另一種結(jié)構(gòu)。
我們可以將一個(gè)結(jié)構(gòu)轉(zhuǎn)換為字節(jié):
public struct Test { public int A; public int B; public int C; } ... ... Test test = new Test() { A = 1, B = 2, C = 3 }; var testArray = new Test[] { test }; ReadOnlySpan<byte> tmp = MemoryMarshal.AsBytes(testArray.AsSpan()); // socket.Send(tmp); ...
還可以逆向還原字節(jié)為結(jié)構(gòu)體:
// bytes = socket.Accept(); .. ReadOnlySpan<Test> testSpan = MemoryMarshal.Cast<byte,Test>(tmp); // or Test testSpan = MemoryMarshal.Read<Test>(tmp);
例如,我們要對(duì)比兩個(gè)結(jié)構(gòu)體數(shù)組中,每個(gè)結(jié)構(gòu)體是否相等,可以采用以下代碼:
static void Main(string[] args) { int[] a = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int[] b = new int[] { 1, 2, 3, 4, 5, 6, 7, 0, 9 }; _ = Compare64(a,b); } private static bool Compare64<T>(T[] t1, T[] t2) where T : struct { var l1 = MemoryMarshal.Cast<T, long>(t1); var l2 = MemoryMarshal.Cast<T, long>(t2); for (int i = 0; i < l1.Length; i++) { if (l1[i] != l2[i]) return false; } return true; }
后面有個(gè)更好的性能提升方案。
程序員基本都學(xué)習(xí)過(guò) C 語(yǔ)言,應(yīng)該了解 C 語(yǔ)言中的結(jié)構(gòu)體字節(jié)對(duì)齊,在 C# 中也是一樣,兩種類(lèi)型相互轉(zhuǎn)換,除了 C# 結(jié)構(gòu)體轉(zhuǎn) C# 結(jié)構(gòu)體,也可以 C 語(yǔ)言結(jié)構(gòu)體轉(zhuǎn) C# 結(jié)構(gòu)體,但是要考慮好字節(jié)對(duì)齊,如果兩個(gè)結(jié)構(gòu)體所占用的內(nèi)存大小不一樣,則可能在轉(zhuǎn)換時(shí)出現(xiàn)數(shù)據(jù)丟失或出現(xiàn)錯(cuò)誤。
4,Marshal
Marshal 提供了用于分配非托管內(nèi)存,復(fù)制非托管內(nèi)存塊以及將托管類(lèi)型轉(zhuǎn)換為非托管類(lèi)型的方法的集合,以及與非托管代碼進(jìn)行交互時(shí)使用的其他方法,或者用來(lái)確定對(duì)象的大小。
例如,來(lái)確定 C# 中的一些類(lèi)型大?。?/p>
Console.WriteLine("SystemDefaultCharSize={0}, SystemMaxDBCSCharSize={1}", Marshal.SystemDefaultCharSize, Marshal.SystemMaxDBCSCharSize);
輸出 char 占用的字節(jié)數(shù)。
例如,在調(diào)用非托管代碼時(shí),需要傳遞函數(shù)指針,C# 一般使用委托傳遞,很多時(shí)候?yàn)榱吮苊飧鞣N內(nèi)存問(wèn)題異常問(wèn)題,需要轉(zhuǎn)換為指針傳遞。
IntPtr p = Marshal.GetFunctionPointerForDelegate(_overrideCompileMethod)
Marshal 也可以很方便地獲得一個(gè)結(jié)構(gòu)體的字節(jié)大?。?/p>
public struct Point { public Int32 x, y; } Marshal.SizeOf(typeof(Point));
從非托管內(nèi)存中分配一塊內(nèi)存和釋放內(nèi)存,我們可以避免 usafe 代碼的使用,代碼示例:
IntPtr hglobal = Marshal.AllocHGlobal(100); Marshal.FreeHGlobal(hglobal);
實(shí)踐
合理利用前面提到的二進(jìn)制處理類(lèi),可以在很多方面提升代碼性能,在前面的學(xué)習(xí)中,我們大概了解這些對(duì)象,但是有什么應(yīng)用場(chǎng)景?真的能夠提升性能?有沒(méi)有練習(xí)代碼?
這里筆者舉個(gè)例子,如何比較兩個(gè) byte[] 數(shù)組是否相等?
最簡(jiǎn)單的代碼示例如下:
public bool ForBytes(byte[] a,byte[] b) { if (a.Length != b.Length) return false; for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) return false; } return true; }
這個(gè)代碼很簡(jiǎn)單,循環(huán)遍歷字節(jié)數(shù)組,一個(gè)個(gè)判斷是否相等。
如果用上前面的二進(jìn)制處理對(duì)象類(lèi),則可以這樣寫(xiě)代碼:
private static bool EqualsBytes(byte[] b1, byte[] b2) { var a = b1.AsSpan(); var b = b2.AsSpan(); Span<byte> copy1 = default; Span<byte> copy2 = default; if (a.Length != b.Length) return false; for (int i = 0; i < a.Length;) { if (a.Length - 8 > i) { copy1 = a.Slice(i, 8); copy2 = b.Slice(i, 8); if (BinaryPrimitives.ReadUInt64BigEndian(copy1) != BinaryPrimitives.ReadUInt64BigEndian(copy2)) return false; i += 8; continue; } if (a[i] != b[i]) return false; i++; } return true; }
你可能會(huì)在想,第二種方法,這么多代碼,這么多判斷,還有各種函數(shù)調(diào)用,還多創(chuàng)建了一些對(duì)象,這特么能夠提升速度?這樣會(huì)不會(huì)消耗更多內(nèi)存??? 別急,你可以使用以下完整代碼測(cè)試:
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; using System; using System.Buffers.Binary; using System.Runtime.InteropServices; using System.Text; namespace BenTest { [SimpleJob(RuntimeMoniker.NetCoreApp31)] [SimpleJob(RuntimeMoniker.CoreRt31)] [RPlotExporter] public class Test { private byte[] _a = Encoding.UTF8.GetBytes("5456456456444444444444156456454564444444444444444444444444444444444444444777777777777777777777711111111111116666666666666"); private byte[] _b = Encoding.UTF8.GetBytes("5456456456444444444444156456454564444444444444444444444444444444444444444777777777777777777777711111111111116666666666666"); private int[] A1 = new int[] { 41544444, 4487, 841, 8787, 4415, 7, 458, 4897, 87897, 815, 485, 4848, 787, 41, 5489, 74878, 84, 89787, 8456, 4857489, 784, 85489, 47 }; private int[] B2 = new int[] { 41544444, 4487, 841, 8787, 4415, 7, 458, 4897, 87897, 815, 485, 4848, 787, 41, 5489, 74878, 84, 89787, 8456, 4857489, 784, 85489, 47 }; [Benchmark] public bool ForBytes() { for (int i = 0; i < _a.Length; i++) { if (_a[i] != _b[i]) return false; } return true; } [Benchmark] public bool ForArray() { return ForArray(A1, B2); } private bool ForArray<T>(T[] b1, T[] b2) where T : struct { for (int i = 0; i < b1.Length; i++) { if (!b1[i].Equals(b2[i])) return false; } return true; } [Benchmark] public bool EqualsArray() { return EqualArray(A1, B2); } [Benchmark] public bool EqualsBytes() { var a = _a.AsSpan(); var b = _b.AsSpan(); Span<byte> copy1 = default; Span<byte> copy2 = default; if (a.Length != b.Length) return false; for (int i = 0; i < a.Length;) { if (a.Length - 8 > i) { copy1 = a.Slice(i, 8); copy2 = b.Slice(i, 8); if (BinaryPrimitives.ReadUInt64BigEndian(copy1) != BinaryPrimitives.ReadUInt64BigEndian(copy2)) return false; i += 8; continue; } if (a[i] != b[i]) return false; i++; } return true; } private bool EqualArray<T>(T[] t1, T[] t2) where T : struct { Span<byte> b1 = MemoryMarshal.AsBytes<T>(t1.AsSpan()); Span<byte> b2 = MemoryMarshal.AsBytes<T>(t2.AsSpan()); Span<byte> copy1 = default; Span<byte> copy2 = default; if (b1.Length != b2.Length) return false; for (int i = 0; i < b1.Length;) { if (b1.Length - 8 > i) { copy1 = b1.Slice(i, 8); copy2 = b2.Slice(i, 8); if (BinaryPrimitives.ReadUInt64BigEndian(copy1) != BinaryPrimitives.ReadUInt64BigEndian(copy2)) return false; i += 8; continue; } if (b1[i] != b2[i]) return false; i++; } return true; } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<Test>(); Console.ReadKey(); } } }
使用 BenchmarkDotNet 的測(cè)試結(jié)果如下:
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1052 (21H1/May2021Update) Intel Core i7-10700 CPU 2.90GHz, 1 CPU, 16 logical and 8 physical cores .NET SDK=5.0.301 [Host] : .NET Core 3.1.16 (CoreCLR 4.700.21.26205, CoreFX 4.700.21.26205), X64 RyuJIT .NET Core 3.1 : .NET Core 3.1.16 (CoreCLR 4.700.21.26205, CoreFX 4.700.21.26205), X64 RyuJIT | Method | Job | Runtime | Mean | Error | StdDev | |------------ |-------------- |-------------- |---------:|---------:|---------:| | ForBytes | .NET Core 3.1 | .NET Core 3.1 | 76.95 ns | 0.064 ns | 0.053 ns | | ForArray | .NET Core 3.1 | .NET Core 3.1 | 66.37 ns | 1.258 ns | 1.177 ns | | EqualsArray | .NET Core 3.1 | .NET Core 3.1 | 17.91 ns | 0.027 ns | 0.024 ns | | EqualsBytes | .NET Core 3.1 | .NET Core 3.1 | 26.26 ns | 0.432 ns | 0.383 ns |
可以看到,byte[] 比較中,使用了二進(jìn)制對(duì)象的方式,耗時(shí)下降了近 60ns,而在 struct 的比較中,耗時(shí)也下降了 40ns。
在第二種代碼中,我們使用了 Span、切片、 MemoryMarshal、BinaryPrimitives,這些用法都可以給我們的程序性能帶來(lái)很大的提升。
這里示例雖然使用了 Span 等,其最主要是利用了 64位 CPU ,64位 CPU 能夠一次性讀取 8個(gè)字節(jié)(64位),因此我們使用 ReadUInt64BigEndian
一次讀取從字節(jié)數(shù)組中讀取 8 個(gè)字節(jié)去進(jìn)行比較。如果字節(jié)數(shù)組長(zhǎng)度為 1024 ,那么第二種方法只需要 比較 128次。
當(dāng)然,這里并不是這種代碼性能是最強(qiáng)的,因?yàn)?CLR 有很多底層方法具有更猛的性能。不過(guò),我們也看到了,合理使用這些類(lèi)型,能夠很大程度上提高代碼性能。上面的數(shù)組對(duì)比只是一個(gè)簡(jiǎn)單的例子,在實(shí)際項(xiàng)目中,我們也可以挖掘更多使用場(chǎng)景。
更高性能
雖然第二種方法,快了幾倍,但是性能還不夠強(qiáng)勁,我們可以利用 Span 中的 API,來(lái)實(shí)現(xiàn)更快的比較。
[Benchmark] public bool SpanEqual() { return SpanEqual(_a,_b); } private bool SpanEqual(byte[] a, byte[] b) { return a.AsSpan().SequenceEqual(b); }
可以試試
StructuralComparisons.StructuralEqualityComparer.Equals(a, b);
性能測(cè)試結(jié)果:
| Method | Job | Runtime | Mean | Error | StdDev | |------------ |-------------- |-------------- |----------:|----------:|----------:| | ForBytes | .NET Core 3.1 | .NET Core 3.1 | 77.025 ns | 0.0502 ns | 0.0419 ns | | ForArray | .NET Core 3.1 | .NET Core 3.1 | 66.192 ns | 0.6127 ns | 0.5117 ns | | EqualsArray | .NET Core 3.1 | .NET Core 3.1 | 17.897 ns | 0.0122 ns | 0.0108 ns | | EqualsBytes | .NET Core 3.1 | .NET Core 3.1 | 25.722 ns | 0.4584 ns | 0.4287 ns | | SpanEqual | .NET Core 3.1 | .NET Core 3.1 | 4.736 ns | 0.0099 ns | 0.0093 ns |
可以看到,Span.SequenceEqual()
的速度簡(jiǎn)直是碾壓。
對(duì)于 C# 中的二進(jìn)制處理技巧就介紹到這里,閱讀 CLR 源碼 時(shí),我們可以學(xué)習(xí)到很多騷操作,讀者可以多閱讀 CLR 源碼,對(duì)技術(shù)提升有很大的幫助。
到此這篇關(guān)于C#處理類(lèi)型和二進(jìn)制數(shù)據(jù)轉(zhuǎn)換并提高程序性能的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
c#多線程中Lock()關(guān)鍵字的用法小結(jié)
本篇文章主要是對(duì)c#多線程中Lock()關(guān)鍵字的用法進(jìn)行了詳細(xì)的總結(jié)介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01C# 操作 access 數(shù)據(jù)庫(kù)的實(shí)例代碼
這篇文章主要介紹了C# 操作 access 數(shù)據(jù)庫(kù)的實(shí)例代碼,需要的朋友可以參考下2018-03-03C# Winform 調(diào)用系統(tǒng)接口操作 INI 配置文件的代碼
封裝了一小段代碼, 調(diào)用系統(tǒng)接口, 操作配置文件. 一般用于 .ini 文件, 或者其它鍵值對(duì)格式的配置文件2011-05-05C# winForm實(shí)現(xiàn)的氣泡提示窗口功能示例
這篇文章主要介紹了C# winForm實(shí)現(xiàn)的氣泡提示窗口功能,涉及C# winForm窗口屬性與設(shè)置相關(guān)操作技巧,需要的朋友可以參考下2018-03-03C#之Expression表達(dá)式樹(shù)實(shí)例
這篇文章主要介紹了C#之Expression表達(dá)式樹(shù),包括了表達(dá)式樹(shù)的原理與用法技巧,需要的朋友可以參考下2014-10-10c#設(shè)計(jì)模式之單例模式的實(shí)現(xiàn)方式
這篇文章主要給大家介紹了關(guān)于c#設(shè)計(jì)模式之單例模式的實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用c#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11C#實(shí)現(xiàn)的上傳圖片、保存圖片、加水印、生成縮略圖功能示例
這篇文章主要介紹了C#實(shí)現(xiàn)的上傳圖片、保存圖片、加水印、生成縮略圖功能,結(jié)合實(shí)例形式較為詳細(xì)的分析了C#圖片上傳、保存、水印、縮略圖等相關(guān)操作技巧,需要的朋友可以參考下2019-02-02C#訪問(wèn)網(wǎng)絡(luò)共享文件夾的方法
這篇文章主要為大家詳細(xì)介紹了C#訪問(wèn)網(wǎng)絡(luò)共享文件夾的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05C#實(shí)現(xiàn)判斷當(dāng)前操作用戶管理角色的方法
這篇文章主要介紹了C#實(shí)現(xiàn)判斷當(dāng)前操作用戶管理角色的方法,涉及C#針對(duì)系統(tǒng)用戶判斷的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08