stackalloc
使用栈内存,减少GC压力
var wordMatchCounts = stackalloc float[wordCount];
Span
Span 支持 reinterpret_cast 的理念,即可以将 Span 强制转换为 Span
Span 支持 reinterpret_cast 的理念,即可以将 Span
Span 也能装在集合之ValueListBuilder & .AsSpan()
.NET 内部提升性能对象:ValueListBuilder & .AsSpan()
ValueListBuilder & .AsSpan()
.NET Core 源码中的内部提升性能对象:ValueListBuilder & .AsSpan()
它在 String.Replace 中被使用
public unsafe string Replace(string oldValue, string? newValue) {ArgumentException.ThrowIfNullOrEmpty(oldValue, nameof (oldValue));if (newValue == null) newValue = string.Empty;// ISSUE: untyped stack allocationValueListBuilder<int> valueListBuilder = new ValueListBuilder<int>(new Span<int>((void*) __untypedstackalloc(new IntPtr(512)), 128));if (oldValue.Length == 1){if (newValue.Length == 1)return this.Replace(oldValue[0], newValue[0]);char ch = oldValue[0];int elementOffset = 0;while (true){int num = SpanHelpers.IndexOf(ref Unsafe.Add<char>(ref this._firstChar, elementOffset), ch, this.Length - elementOffset);if (num >= 0){valueListBuilder.Append(elementOffset + num);elementOffset += num + 1;}else break;}}else{int elementOffset = 0;while (true){int num = SpanHelpers.IndexOf(ref Unsafe.Add<char>(ref this._firstChar, elementOffset), this.Length - elementOffset, ref oldValue._firstChar, oldValue.Length);if (num >= 0){valueListBuilder.Append(elementOffset + num);elementOffset += num + oldValue.Length;}else break;}}if (valueListBuilder.Length == 0) eturn this;string str = this.ReplaceHelper(oldValue.Length, newValue, **valueListBuilder.AsSpan()**);valueListBuilder.Dispose();return str;}
.NET 内部类直接将集合转回为 Span<T>:CollectionsMarshal.AsSpan<string>(List<string>)
private static unsafe string JoinCore<T>(ReadOnlySpan<char> separator, IEnumerable<T> values){if (typeof (T) == typeof (string)){if (values is List<string> list)return string.JoinCore(separator, (ReadOnlySpan<string>) CollectionsMarshal.AsSpan<string>(list));if (values is string[] array)return string.JoinCore(separator, new ReadOnlySpan<string>(array));}
ref struct,使用ref读取值类型,避免值类型拷贝
使用ref读取值类型,避免值类型拷贝,但要注意对当前值类型的修改,会影响被ref的那个值类型,因为本质上你在操作一个指针
ref var hierarchy = ref ph[i];
ref var words = ref hierarchy.Words;
Unsafe.IsNullRef
可以使用 Unsafe.IsNullRef
来判断一个 ref
是否为空。如果用户没有对 Foo.X
进行初始化,则默认是空引用:
ref struct Foo {public ref int X;public bool IsNull => Unsafe.IsNullRef(ref X);public Foo(ref int x) { X = ref x; }
}
1.4 NativeMemory
相比 Marshal.AllocHGlobal 和 Marshal.FreeHGlobal,其实现在更推荐 NativeMemory.*,有诸多好处:
-
支持控制是否零初始化
-
支持控制内存对齐
-
参数是 nuint 类型,支持在 64 位进程上支持分配超过 int 上限的大小
1.5 struct 直接转换内存数据
1.5.1 C#使用struct直接转换下位机数据
数据结构
假定下位机(C语言编写)给到我们的数据结构是这个,传输方式为小端方式
typedef struct {unsigned long int time; // 4个字节float tmpr[3]; // 4*3 个字节float forces[6]; // 4*6个字节float distance[6]; // 4*6个字节} dataItem_t;
方法1(略麻烦)
首先需要定义一个struct:
[StructLayout(LayoutKind.Sequential, Size = 64, Pack = 1)]
public struct HardwareData {//[FieldOffset(0)]public UInt32 Time; // 4个字节[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]//[FieldOffset(4)]public float[] Tmpr; // 3* 4个字节//[FieldOffset(16)][MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]public float[] Forces; // 6* 4个字节//[FieldOffset(40)][MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]public float[] Distance; // 6*4个字节}
然后使用以下代码进行转换
// converts byte[] to struct
public static T RawDeserialize(byte[] rawData, int position) {int rawsize = Marshal.SizeOf(typeof(T));if (rawsize > rawData.Length - position) throw new ArgumentException("Not enough data to fill struct. Array length from position: " + (rawData.Length - position) + ", Struct length: " + rawsize);IntPtr buffer = Marshal.AllocHGlobal(rawsize);Marshal.Copy(rawData, position, buffer, rawsize);T retobj = (T)Marshal.PtrToStructure(buffer, typeof(T));Marshal.FreeHGlobal(buffer);return retobj;
}// converts a struct to byte[]
public static byte[] RawSerialize(object anything) {int rawSize = Marshal.SizeOf(anything);IntPtr buffer = Marshal.AllocHGlobal(rawSize);Marshal.StructureToPtr(anything, buffer, false);byte[] rawDatas = new byte[rawSize];Marshal.Copy(buffer, rawDatas, 0, rawSize);Marshal.FreeHGlobal(buffer);return rawDatas;
}
注意这里我使用的方式为LayoutKind.Sequential,如果直接使用LayoutKind.Explicit并设置FieldOffset会弹出一个诡异的错误System.TypeLoadException:"Could not load type 'ConsoleApp3.DataItem' from assembly 'ConsoleApp3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field."。
方法2
既然用上了 unsafe,就干脆直接一点。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct DataItem {public UInt32 time; // 4个字节public fixed float tmpr[3]; // 3* 4个字节public fixed float forces[6]; // 6* 4个字节public fixed float distance[6]; // 6*4个字节
}
这样,获得数组可以直接正常访问了。
C#使用struct直接转换下位机数据
https://blog.51cto.com/u_15127641/2754559
1.6 string.Join 内部实现解析
CollectionsMarshal.AsSpan(valuesList)
if (values is List<string?> valuesList) {return JoinCore(separator.AsSpan(), CollectionsMarshal.AsSpan(valuesList));
}
if (values is string?[] valuesArray)
{return JoinCore(separator.AsSpan(), new ReadOnlySpan<string?>(valuesArray));
}
Join
public static string Join(string? separator, IEnumerable<string?> values)
{if (values is List<string?> valuesList){return JoinCore(separator.AsSpan(), CollectionsMarshal.AsSpan(valuesList));}if (values is string?[] valuesArray){return JoinCore(separator.AsSpan(), new ReadOnlySpan<string?>(valuesArray));}if (values == null){ThrowHelper.ThrowArgumentNullException(ExceptionArgument.values);}using (IEnumerator<string?> en = values.GetEnumerator()){if (!en.MoveNext()){return Empty;}string? firstValue = en.Current;if (!en.MoveNext()){// Only one value availablereturn firstValue ?? Empty;}// Null separator and values are handled by the StringBuildervar result = new ValueStringBuilder(stackalloc char[256]);result.Append(firstValue);do{result.Append(separator);result.Append(en.Current);}while (en.MoveNext());return result.ToString();}
}
JoinCore
private static string JoinCore(ReadOnlySpan<char> separator, ReadOnlySpan<string?> values)
{if (values.Length <= 1){return values.IsEmpty ?Empty :values[0] ?? Empty;}long totalSeparatorsLength = (long)(values.Length - 1) * separator.Length;if (totalSeparatorsLength > int.MaxValue){ThrowHelper.ThrowOutOfMemoryException();}int totalLength = (int)totalSeparatorsLength;// Calculate the length of the resultant string so we know how much space to allocate.foreach (string? value in values){if (value != null){totalLength += value.Length;if (totalLength < 0) // Check for overflow{ThrowHelper.ThrowOutOfMemoryException();}}}// Copy each of the strings into the result buffer, interleaving with the separator.string result = FastAllocateString(totalLength);int copiedLength = 0;for (int i = 0; i < values.Length; i++){// It's possible that another thread may have mutated the input array// such that our second read of an index will not be the same string// we got during the first read.// We range check again to avoid buffer overflows if this happens.if (values[i] is string value){int valueLen = value.Length;if (valueLen > totalLength - copiedLength){copiedLength = -1;break;}// Fill in the value.FillStringChecked(result, copiedLength, value);copiedLength += valueLen;}if (i < values.Length - 1){// Fill in the separator.// Special-case length 1 to avoid additional overheads of CopyTo.// This is common due to the char separator overload.ref char dest = ref Unsafe.Add(ref result._firstChar, copiedLength);if (separator.Length == 1){dest = separator[0];}else{separator.CopyTo(new Span<char>(ref dest, separator.Length));}copiedLength += separator.Length;}}// If we copied exactly the right amount, return the new string. Otherwise,// something changed concurrently to mutate the input array: fall back to// doing the concatenation again, but this time with a defensive copy. This// fall back should be extremely rare.return copiedLength == totalLength ?result :JoinCore(separator, values.ToArray().AsSpan());
}