C# 使用 stackalloc 在栈上分配内存
stackalloc 官方文档
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc
A stackalloc
expression allocates a block of memory on the stack. A stack-allocated memory block created during the method execution is automatically discarded when that method returns. You can't explicitly free the memory allocated with stackalloc
. A stack allocated memory block isn't subject to garbage collection and doesn't have to be pinned with a fixed statement.
stackalloc
表达式会在栈上分配内存块。在函数运行时创建的栈上的内存块会在函数返回时自动销毁。你不能显式地释放stackalloc
分配的内存。栈上内存块不受垃圾回收影响并且不需要使用fixed语句。
你可以将stackalloc
表达式的结果赋值给以下类型的变量:
- System.Span
or System.ReadOnlySpan , - A pointer type
int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
unsafe
{
int length = 3;
int* numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
}
The amount of memory available on the stack is limited.
栈上的内存数量是有限的,避免在循环中使用stackalloc
。
The use of stackalloc
automatically enables buffer overrun detection features in the common language runtime (CLR). If a buffer overrun is detected, the process is terminated as quickly as possible to minimize the chance that malicious code is executed.
实战应用
怪物掉落这种涉及资源分配的逻辑,必然需要放在服务端执行。而且执行频率和玩家杀怪速度相关。而击杀怪物基本上就是游戏核心逻辑的一部分,会被频繁执行。因此我们可以通过将怪物掉落相关的内存开销放在栈中来避免或者延迟垃圾回收的触发。 此处需要注意,stackalloc后面的类型需要是struct值类型。
public void DropLoots(Monster monster)
{
int lootCount = GetLootCount(monster);
//how to avoid stack overflow
Span<MonsterLoot> lootResultSpan = stackalloc MonsterLoot[lootCount];
Span<MonsterLoot> lootDropSpan = stackalloc MonsterLoot[monster.config.LootList.Count];
for(int i = 0; i < monster.config.LootList.Count; i++)
{
lootDropSpan[i] = monster.config.LootList[i];
}
for (int i = 0; i < lootCount; ++i)
{
var loot = Get1Loot(ref lootDropSpan);
lootResultSpan[i] = loot;
}
}
private MonsterLoot Get1Loot(ref Span<MonsterLoot> lootDropSpan)
{
int totalWeight = 0;
foreach (var loot in lootDropSpan)
{
if (loot.DropCountLimit == 0)
continue;
totalWeight += loot.DropWeight;
}
float randomWeight = WildBoar.Random.Float01() * totalWeight;
Debug.Log($"DropLoot totalWeight={totalWeight} randomWeight={randomWeight}");
int accumulateWeight = 0;
for(int i = 0; i < lootDropSpan.Length; i++)
{
var loot = lootDropSpan[i];
if (loot.DropCountLimit == 0)
continue;
accumulateWeight += loot.DropWeight;
if (accumulateWeight >= randomWeight)
{
lootDropSpan[i].DropCountLimit--;
return loot;
}
}
throw new System.Exception("shouldnt reach here");
}