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 allocation
ValueListBuilder<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 available
return firstValue ?? Empty;
}
// Null separator and values are handled by the StringBuilder
var 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());
}