- Não tema o ceifador
- Vida na pista rápida
- Siga seu próprio caminho. Parte um. Pilha
- Siga seu próprio caminho. Parte dois. Montão
Este é o terceiro de uma série no GC. No primeiro artigo, apresentei o coletor de lixo D e os recursos de linguagem que o exigem, além de abordar técnicas simples para usá-lo com eficiência. No segundo artigo, mostrei quais ferramentas e bibliotecas existem na linguagem e nas bibliotecas que limitam a GC em determinados lugares do código, e como o compilador pode ajudar a identificar lugares onde vale a pena fazer, e também recomendei que, ao escrever programas em D, primeiro use a GC com ousadia, enquanto minimizando seu impacto no desempenho com estratégias simples e, em seguida, aprimorando o código para evitar o GC ou otimizar ainda mais seu uso apenas quando o criador de perfil o garantir.
Quando o coletor de lixo é desabilitado GC.disable
ou desabilitado por um atributo de função @nogc
, a memória ainda precisa ser alocada em algum lugar. E mesmo se você usar o GC ao máximo, ainda é recomendável minimizar a quantidade e o número de alocações de memória por meio do GC. Isso significa alocar memória na pilha ou na pilha regular. Este artigo se concentrará no primeiro. A alocação de memória no heap será o assunto do próximo artigo.
Alocando memória na pilha
A estratégia mais simples de alocação de memória em D é a mesma de C: evite usar o heap e use a pilha o máximo possível. Se uma matriz for necessária e seu tamanho for conhecido no momento da compilação, use uma matriz estática em vez de dinâmica. As estruturas têm semântica de valor e são criadas na pilha por padrão, enquanto as classes têm semântica de referência e geralmente são criadas no heap; estruturas devem ser preferidas sempre que possível. Os recursos D em tempo de compilação o ajudam a conseguir muitas coisas que de outra forma não seriam possíveis.
Matrizes estáticas
Matrizes estáticas em D exigem que o tamanho seja conhecido em tempo de compilação.
// OK
int[10] nums;
// : x
int x = 10;
int[x] err;
Diferentemente das matrizes dinâmicas, a inicialização de matrizes estáticas por meio de um literal não aloca memória pelo GC. Os comprimentos da matriz e do literal devem corresponder, caso contrário, o compilador gerará um erro.
@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 .
No próximo artigo da série, veremos maneiras de alocar memória em uma pilha regular sem passar pelo GC.