- 不要害怕收割者
- 快速生活
- 走自己的路。第一部分。叠放
- 走自己的路。第二部分。一堆
这是GC系列文章中的第三篇。在第一篇文章中,我介绍了D垃圾收集器和需要它的语言功能,还介绍了有效使用它的简单技术。在第二篇文章中,我展示了语言和库中存在哪些工具会在代码的某些位置上限制GC,以及编译器如何帮助您确定值得做的地方,并且还建议在D中编写程序时首先使用大胆使用GC,同时通过简单的策略将其对性能的影响降到最低,然后调整代码以避免GC,甚至仅在分析器保证的情况下进一步优化其使用。
当垃圾回收器被禁用GC.disable
或被function属性禁用时@nogc
,仍然需要在某个地方分配内存。而且,即使您最大程度地使用GC,还是建议您尽量减少通过GC分配的内存数量和数量。这意味着在堆栈或常规堆上分配内存。本文将重点讨论前者。堆上的内存分配将是下一篇文章的主题。
在堆栈上分配内存
D中最简单的内存分配策略与C中相同:避免使用堆,并尽可能使用堆栈。如果需要一个数组,并且在编译时知道其大小,请使用静态数组而不是动态数组。结构具有值语义,默认情况下在堆栈上创建,而类具有引用语义,通常在堆上创建;只要有可能,结构应该是首选。编译时的D功能可帮助您实现许多其他无法完成的任务。
静态数组
D中的静态数组要求在编译时知道大小。
// OK
int[10] nums;
// : x
int x = 10;
int[x] err;
与动态数组不同,通过文字初始化静态数组不会通过GC分配内存。数组和文字的长度必须匹配,否则编译器将生成错误。
@nogc void main() {
int[3] nums = [1, 2, 3];
}
, , , .
void printNums(int[] nums) {
import std.stdio : writeln;
writeln(nums);
}
void main() {
int[] dnums = [0, 1, 2];
int[3] snums = [0, 1, 2];
printNums(dnums);
printNums(snums);
}
-vgc
, , — . :
int[] foo() {
auto nums = [0, 1, 2];
// - nums...
return nums;
}
nums
. , , . , .
, - , , . , . .dup
:
int[] foo() {
int[3] nums = [0, 1, 2];
// x — - nums
bool condition = x;
if(condition) return nums.dup;
else return [];
}
GC .dup
, . , []
null
— , ( length
) 0, ptr
null
.
D , . , , : .
struct Foo {
int x;
~this() {
import std.stdio;
writefln("#%s says bye!", x);
}
}
void main() {
Foo f1 = Foo(1);
Foo f2 = Foo(2);
Foo f3 = Foo(3);
}
, :
#3 says bye!
#2 says bye!
#1 says bye!
, , . GC new
, GC . , . [std.typecons.scoped](https://dlang.org/phobos/std_typecons.html#.scoped)
.
class Foo {
int x;
this(int x) {
this.x = x;
}
~this() {
import std.stdio;
writefln("#%s says bye!", x);
}
}
void main() {
import std.typecons : scoped;
auto f1 = scoped!Foo(1);
auto f2 = scoped!Foo(2);
auto f3 = scoped!Foo(3);
}
, , . core.object.destroy
, .
, scoped
, destroy
@nogc
-. , , GC, , @nogc
-. , nogc
, .
, . (Plain Old Data, POD) , - GUI, , . , , . , , , .
alloca
C D « », alloca
. , GC, . , :
import core.stdc.stdlib : alloca;
void main() {
size_t size = 10;
void* mem = alloca(size);
// Slice the memory block
int[] arr = cast(int[])mem[0 .. size];
}
C, alloca
: . , arr
. arr.dup
.
, Queue
, «». D , . Java , , . D , (Design by Introspection). , , , , UFCS, ( ).
DbI . (. .)
Queue
. , , . , : - . , , , .
// `Size` 0
// ;
//
struct Queue(T, size_t Size = 0)
{
// .
// `public` DbI-
// , Queue .
enum isFixedSize = Size > 0;
void enqueue(T item)
{
static if(isFixedSize) {
assert(_itemCount < _items.length);
}
else {
ensureCapacity();
}
push(item);
}
T dequeue() {
assert(_itemCount != 0);
static if(isFixedSize) {
return pop();
}
else {
auto ret = pop();
ensurePacked();
return ret;
}
}
//
static if(!isFixedSize) {
void reserve(size_t capacity) {
/* */
}
}
private:
static if(isFixedSize) {
T[Size] _items;
}
else T[] _items;
size_t _head, _tail;
size_t _itemCount;
void push(T item) {
/* item, _head _tail */
static if(isFixedSize) { ... }
else { ... }
}
T pop() {
/* item, _head _tail */
static if(isFixedSize) { ... }
else { ... }
}
//
static if(!isFixedSize) {
void ensureCapacity() { /* , */ }
void ensurePacked() { /* , */}
}
}
:
Queue!Foo qUnbounded;
Queue!(Foo, 128) qBounded;
qBounded
. qUnbounded, . , . isFixedSize
:
void doSomethingWithQueueInterface(T)(T queue)
{
static if(T.isFixedSize) { ... }
else { ... }
}
: __traits(hasMember, T, "reserve")
, — : hasMember!T("reserve")
. __traits
std.traits
— DbI; .
GC. , , — GC .
在本系列的下一篇文章中,我们将研究无需通过GC即可在常规堆上分配内存的方法。