- Ne craignez pas le faucheur
- La vie à vitesse grand V
- Suis ton propre chemin. Partie un. Empiler
- Suis ton propre chemin. Deuxième partie. Un tas
Il s'agit du troisième d'une série sur GC. Dans le premier article, j'ai présenté le ramasse-miettes D et les fonctionnalités de langage qui le nécessitent, et abordé des techniques simples pour l'utiliser efficacement. Dans le deuxième article, j'ai montré quels outils dans le langage et les bibliothèques existent qui limitent GC à certains endroits du code, et comment le compilateur peut aider à identifier les endroits où cela vaut la peine d'être fait, et également recommandé lors de l'écriture de programmes en D, tout d'abord, utilisez hardiment GC, tout en minimiser son impact sur les performances avec des stratégies simples, puis peaufiner le code pour éviter GC ou encore optimiser son utilisation uniquement là où le profileur le garantit.
Lorsque le garbage collector est désactivé GC.disable
ou désactivé par un attribut de fonction @nogc
, la mémoire doit encore être allouée quelque part. Et même si vous utilisez GC au maximum, il est toujours conseillé de minimiser la quantité et le nombre d'allocations de mémoire via le GC. Cela signifie allouer de la mémoire sur la pile ou sur le tas normal. Cet article se concentrera sur le premier. L'allocation de mémoire sur le tas fera l'objet du prochain article.
Allocation de mémoire sur la pile
La stratégie d'allocation de mémoire la plus simple en D est la même qu'en C: évitez d'utiliser le tas et utilisez la pile autant que possible. Si un tableau est nécessaire et que sa taille est connue au moment de la compilation, utilisez un tableau statique au lieu d'un tableau dynamique. Les structures ont une sémantique de valeur et sont créées sur la pile par défaut, tandis que les classes ont une sémantique de référence et sont généralement créées sur le tas; les structures devraient être préférées chaque fois que possible. Les capacités D au moment de la compilation vous aident à réaliser de nombreuses choses qui ne seraient pas possibles autrement.
Tableaux statiques
Les tableaux statiques en D nécessitent que la taille soit connue au moment de la compilation.
// OK
int[10] nums;
// : x
int x = 10;
int[x] err;
Contrairement aux tableaux dynamiques, l'initialisation de tableaux statiques via un littéral n'alloue pas de mémoire via le GC. Les longueurs du tableau et du littéral doivent correspondre, sinon le compilateur générera une erreur.
@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 .
Dans le prochain article de la série, nous examinerons les moyens d'allouer de la mémoire sur un tas normal sans passer par le GC.