In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
Most people do not understand the knowledge points of this article "C# processing types and binary data conversion and improving program performance", so the editor summarizes the following contents, which are detailed and clear, and have a certain reference value. I hope you can get something after reading this article. Let's take a look at this article entitled "C # handles type and binary data conversion and improves program performance".
C # primitive type
According to memory allocation, C# has value type and reference type.
According to the basic type type, C # has built-in type, common type, custom type, anonymous type, tuple type, CTS type (common type system)
The basic types of C# include:
Integer: sbyte, byte, short, ushort, int, uint, long, ulong
Real number types: float, double, decimal
Character type: char
Boolean type: bool
String type: string
The primitive type in C # is the value type in the underlying type, excluding string. Primitive types can use sizeof () to get the byte size, with two fields MaxValue and MinValue except bool.
Sizeof (uint); uint.MaxValueuint.MinValue
We can also distinguish between generics. The above tutorial types, except string, are all struct.
() where T: struct {} 1, optimize array performance with Buffer
Buffer can manipulate arrays of primitive types (int, byte, etc.) and use the Buffer class in .NET to improve application performance by accessing data in memory faster.
Buffer can fetch a specified number of bytes directly from an array of primitive types, or set a value for a byte.
Buffer mainly manipulates in-memory data and unmanaged memory directly, using Buffer can bring a secure and high-performance experience.
Method description BlockCopy (Array, Int32, Array, Int32, Int32) copies a specified number of bytes from a source array starting at a specific offset to a target array starting at a specific offset. ByteLength (Array) returns the number of bytes in the specified array. GetByte (Array, Int32) retrieves the bytes at the specified position in the specified array. MemoryCopy (Void, Void, Int64, Int64) copies bytes specified as long integer values from one address in memory to another. This API is not CLS compliant. MemoryCopy (Void, Void, UInt64, UInt64) copies bytes specified as unsigned long integer values from one address in memory to another. This API is not CLS compliant. SetByte (Array, Int32, Byte) assigns the specified value to a byte at a specific location in the specified array.
Here are some ways to use Buffer.
BlockCopy can copy part of an array to another array, using the following methods:
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: 01 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19... / / arr1: 01 00 00 02 00 00 03 00 00 00 04 00 00 00 / / arr2: 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() 则可细粒度地设置数组的值,即可以直接设置数组中任意一位的值,其使用方法如下: //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 进制 // 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"); 建议自行测试,断点调试,观察过程。 2,BinaryPrimitives 细粒度操作字节数组 System.Buffers.Binary.BinaryPrimitives 用来以精确的方式读取或者字节数组,只能对 byte 或 byte 数组使用,其使用场景非常广泛。 BinaryPrimitives 的实现原理是 BitConverter,BinaryPrimitives 对 BitConverter 做了一些封装。BinaryPrimitives 的主要使用方式是以某种形式从 byte 或 byte 数组中读取出信息。 例如,BinaryPrimitives 在 byte 数组中,一次性读取四个字节,其示例代码如下: // 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# 中,应该都是小端在前大端在后的,具体可能会因处理器架构而不同。 你可以使用 BitConverter.IsLittleEndian 来判断在当前处理器上,C# 程序是大端还是小端在前。 以 .Read...() 开头的方法,可以以字节为定位访问 byte 数组上的数据。 以 .Write...() 开头的方法,可以向某个位置写入数据。 下面举个例子: // 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); 建议自行测试,断点调试,观察过程。 提高代码安全性 C#和.NET Core 有的许多面向性能的 API,C# 和 .NET 的一大优点是可以在不牺牲内存安全性的情况下编写快速出高性能的库。我们在避免使用 unsafe 代码的情况下,通过二进制处理类,我们可以编写出高性能的代码和具有安全性的代码。 在 C# 中,我们有以下类型可以高效操作字节/内存: Span 和C#类型可以快速安全地访问内存。表示任意内存的连续区域。使用 span 使我们可以序列化为托管.NET数组,堆栈分配的数组或非托管内存,而无需使用指针。.NET可以防止缓冲区溢出。 ref struct 、 Span stackalloc 用于创建基于堆栈的数组。stackalloc 是在需要较小缓冲区时避免分配的有用工具。 低级方法,并在原始类型和字节之间直接转换。MemoryMarshal.GetReference() 、Unsafe.ReadUnaligned() 、Unsafe.WriteUnaligned() BinaryPrimitives具有用于在.NET基本类型和字节之间进行有效转换的辅助方法。例如,读取小尾数字节并返回无符号的64位数字。所提供的方法经过了最优化,并使用了向量化。BinaryPrimitives.ReadUInt64LittleEndian、BinaryPrimitive 以 .Reverse...() 开头的方法,可以置换基元类型的大小端。 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)); // 13,BitConverter、MemoryMarshal BitConverter 可以基元类型和 byte 相互转换,例如 int 和 byte 互转,或者任意取出、写入基元类型的任意一个字节。 其示例如下: // 0b...1_00000100 int value = 260; // byte max value:255 // a = 0b00000100; 丢失 int ... 00000100 之前的位数。 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、ReadOnlyMemory、Span 和 ReadOnlySpan 进行交互操作的方法。 MemoryMarshal 在 System.Runtime.InteropServices 命名空间中。 我们先介绍 MemoryMarshal.Cast(),它可以将一种基元类型的范围强制转换为另一种基元类型的范围。 // 1 int = 4 byte // int [] {1,2} // 0001 0002 var byteArray = new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }; Span byteSpan = byteArray.AsSpan(); // byte to int Span intSpan = MemoryMarshal.Cast(byteSpan); foreach (var item in intSpan) { Console.Write(item + ","); } 最简单的说法是,MemoryMarshal 可以将一种结构转换为另一种结构。 我们可以将一个结构转换为字节: 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 tmp = MemoryMarshal.AsBytes(testArray.AsSpan()); // socket.Send(tmp); ... 还可以逆向还原字节为结构体: // bytes = socket.Accept(); .. ReadOnlySpan testSpan = MemoryMarshal.Cast(tmp); // or Test testSpan = MemoryMarshal.Read(tmp); 例如,我们要对比两个结构体数组中,每个结构体是否相等,可以采用以下代码: 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[] t1, T[] t2) where T : struct { var l1 = MemoryMarshal.Cast(t1); var l2 = MemoryMarshal.Cast(t2); for (int i = 0; i < l1.Length; i++) { if (l1[i] != l2[i]) return false; } return true; } 后面有个更好的性能提升方案。 程序员基本都学习过 C 语言,应该了解 C 语言中的结构体字节对齐,在 C# 中也是一样,两种类型相互转换,除了 C# 结构体转 C# 结构体,也可以 C 语言结构体转 C# 结构体,但是要考虑好字节对齐,如果两个结构体所占用的内存大小不一样,则可能在转换时出现数据丢失或出现错误。 4,Marshal Marshal 提供了用于分配非托管内存,复制非托管内存块以及将托管类型转换为非托管类型的方法的集合,以及与非托管代码进行交互时使用的其他方法,或者用来确定对象的大小。 例如,来确定 C# 中的一些类型大小: Console.WriteLine("SystemDefaultCharSize={0}, SystemMaxDBCSCharSize={1}", Marshal.SystemDefaultCharSize, Marshal.SystemMaxDBCSCharSize); 输出 char 占用的字节数。 例如,在调用非托管代码时,需要传递函数指针,C# 一般使用委托传递,很多时候为了避免各种内存问题异常问题,需要转换为指针传递。 IntPtr p = Marshal.GetFunctionPointerForDelegate(_overrideCompileMethod) Marshal 也可以很方便地获得一个结构体的字节大小: public struct Point{ public Int32 x, y;}Marshal.SizeOf(typeof(Point)); 从非托管内存中分配一块内存和释放内存,我们可以避免 usafe 代码的使用,代码示例: IntPtr hglobal = Marshal.AllocHGlobal(100); Marshal.FreeHGlobal(hglobal);实践 合理利用前面提到的二进制处理类,可以在很多方面提升代码性能,在前面的学习中,我们大概了解这些对象,但是有什么应用场景?真的能够提升性能?有没有练习代码? 这里笔者举个例子,如何比较两个 byte[] 数组是否相等? 最简单的代码示例如下: 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; } 这个代码很简单,循环遍历字节数组,一个个判断是否相等。 如果用上前面的二进制处理对象类,则可以这样写代码: private static bool EqualsBytes(byte[] b1, byte[] b2) { var a = b1.AsSpan(); var b = b2.AsSpan(); Span copy1 = default; Span 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; iTunes;} return true;}
You might be thinking, the second way, so much code, so many judgments, all kinds of function calls, and creating more objects, how can it speed up? Will this consume more memory? Don't worry, you can test with the following complete code:
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 ("5456456444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444777777777777771111111111166666666666666666"); private byte [] _ b = Encoding.UTF8.GetBytes ("54564564564444444415456456444444444444444444444444444444777777777777111111166666666666666666666666") Private int [] A1 = new int [] {41544444, 4487, 841, 8787, 4415, 7, 458, 4897, 87897, 815, 485, 4848, 787, 41, 5489, 74878, 84, 89 787, 8456,485748489, 784, 85489, 47}; private int [] B2 = new int [] {415 4444, 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[] 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 copy1 = default; Span 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; iTunes;} return true;} private bool EqualArray (T [] T1, T [] T2) where T: struct {Span b1 = MemoryMarshal.AsBytes (t1.AsSpan ()) Span b2 = MemoryMarshal.AsBytes (t2.AsSpan ()); Span copy1 = default; Span 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; iTunes;} return true;}} class Program {static void Main (string [] args) {var summary = BenchmarkRunner.Run (); Console.ReadKey () }}}
The test results using BenchmarkDotNet are as follows:
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 |
As you can see, in the byte [] comparison, the use of binary objects reduces the time-consuming by nearly 60ns, while in the struct comparison, the time-consuming also reduces 40ns.
In the second kind of code, we use Span, slicing, MemoryMarshal, BinaryPrimitives, all of which can greatly improve the performance of our program.
Although the example here uses Span and so on, it mainly uses 64-bit CPU. 64-bit CPU can read 8 bytes (64 bits) at a time, so we use ReadUInt64BigEndian to read 8 bytes from the byte array at a time for comparison. If the byte array length is 1024, then the second method only needs to be compared 128 times.
Of course, this kind of code performance is not the best here, because CLR has many underlying methods with stronger performance. However, we have also seen that the rational use of these types can greatly improve code performance. The array comparison above is just a simple example, in the actual project, we can also dig more usage scenarios.
Higher performanc
Although the second method is several times faster, the performance is not strong enough, and we can use API in Span to achieve faster comparisons.
[Benchmark] public bool SpanEqual () {return SpanEqual (_ byte [] a, byte [] b) {return a.AsSpan (). SequenceEqual (b);}
You can try.
StructuralComparisons.StructuralEqualityComparer.Equals (a, b)
Performance test results:
| | Method | Job | Runtime | Mean | Error | StdDev | |-|-| | ForBytes | .NET Core 3.1 | .NET Core | | 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 | ns | SpanEqual | .NET Core 3 | .1 | .NET Core 3.1 | 4.736 ns | 0.0099 ns | 0.0093 ns |
As you can see, the speed of Span.SequenceEqual () is simply crushing.
The above is the content of this article on "C# processing types and binary data conversion and how to improve program performance". I believe we all have some understanding. I hope the content shared by the editor will be helpful to you. If you want to know more about the relevant knowledge, please follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.