Como essa pergunta ficou um pouco congelada na SO, decidi excluí-la e tentar aqui. Se você acha que ele também não se encaixa aqui, pelo menos deixe um comentário na sugestão de como encontrar um exemplo que eu estou procurando ...
Você pode dar um exemplo , onde o uso de V99s C99 oferece uma vantagem real sobre algo como os atuais mecanismos C ++ RAII Ceap-heap atuais?
O exemplo que estou procurando deve:
- Obtenha uma vantagem de desempenho facilmente mensurável (talvez 10%) sobre o uso de heap.
- Não tem uma boa solução alternativa, que não precisaria de toda a matriz.
- Beneficie realmente do tamanho dinâmico, em vez do tamanho máximo fixo.
- É improvável que cause excesso de pilha no cenário de uso normal.
- Seja forte o suficiente para tentar um desenvolvedor que precisa do desempenho para incluir um arquivo de origem C99 em um projeto C ++.
Adicionando alguns esclarecimentos sobre o contexto: refiro-me ao VLA como significou C99 e não incluído no padrão C ++: int array[n]
where n
é uma variável. E estou atrás de um exemplo de caso de uso em que ele supera as alternativas oferecidas por outros padrões (C90, C ++ 11):
int array[MAXSIZE]; // C stack array with compile time constant size
int *array = calloc(n, sizeof int); // C heap array with manual free
int *array = new int[n]; // C++ heap array with manual delete
std::unique_ptr<int[]> array(new int[n]); // C++ heap array with RAII
std::vector<int> array(n); // STL container with preallocated size
Algumas ideias:
- Funções que utilizam varargs, que limitam naturalmente a contagem de itens a algo razoável, ainda não possuem nenhum limite superior útil no nível da API.
- Funções recursivas, onde pilha desperdiçada é indesejável
- Muitas pequenas alocações e liberações, onde a sobrecarga da pilha seria ruim.
- Manipulação de matrizes multidimensionais (como matrizes de tamanho arbitrário), onde o desempenho é crítico, e espera-se que pequenas funções sejam incorporadas muito.
- Do comentário: algoritmo simultâneo, em que a alocação de heap tem sobrecarga de sincronização .
A Wikipedia tem um exemplo que não atende aos meus critérios , porque a diferença prática de usar heap parece irrelevante, pelo menos sem contexto. Também não é ideal, porque sem mais contexto, parece que a contagem de itens pode muito bem causar estouro de pilha.
Nota: Estou especificamente após um código de exemplo, ou sugestão de um algoritmo que se beneficiaria disso, para eu implementar o exemplo eu mesmo.
alloca()
realmente superassemalloc()
em um ambiente multithread por causa da contenção de trava no último . Mas essa é uma extensão real, uma vez que matrizes pequenas devem usar apenas um tamanho fixo, e matrizes grandes provavelmente precisarão da pilha de qualquer maneira.alloca
, que eu acho que são basicamente a mesma coisa). Mas essa coisa multithread é boa, editando a pergunta para incluí-la!malloc
comportamento do Linux está em conformidade com o padrão C.Respostas:
Acabei de hackear um pequeno programa que gera um conjunto de números aleatórios reiniciando na mesma semente a cada vez, para garantir que seja "justo" e "comparável". À medida que avança, ele descobre o mínimo e o máximo desses valores. E quando gera o conjunto de números, conta quantos estão acima da média de
min
emax
.Para matrizes MUITO pequenas, mostra um benefício claro com o término do VLA
std::vector<>
.Não é um problema real, mas podemos facilmente imaginar algo em que estaríamos lendo os valores de um arquivo pequeno, em vez de usar números aleatórios, e fazendo outros cálculos de contagem / min / max mais significativos com o mesmo tipo de código .
Para valores MUITO pequenos do "número de números aleatórios" (x) nas funções relevantes, a
vla
solução vence por uma margem enorme. À medida que o tamanho aumenta, a "vitória" diminui e, com tamanho suficiente, a solução vetorial parece ser MAIS eficiente - não estudou muito essa variante, como quando começamos a ter milhares de elementos em um VLA, não é realmente o que eles deveriam fazer ...E tenho certeza de que alguém me dirá que há alguma maneira de escrever todo esse código com vários modelos e fazê-lo sem executar mais do que o RDTSC e os
cout
bits em tempo de execução ... Mas não acho que seja realmente o ponto.Ao executar esta variante específica, recebo uma diferença de cerca de 10% entre o
func1
(VLA) efunc2
(std :: vector).Isso é compilado com:
g++ -O3 -Wall -Wextra -std=gnu++0x -o vla vla.cpp
Aqui está o código:
fonte
std::vector
.func3
que usa emv.push_back(rand())
vez dev[i] = rand();
e remove a necessidaderesize()
. Demora cerca de 10% a mais em comparação com o usoresize()
. [É claro que, no processo, descobri que o uso dev[i]
é um dos principais contribuintes para o tempo que a função leva - estou um pouco surpreso com isso].std::vector
implementação real que usaria o VLA /alloca
, ou isso é apenas especulação?vector
implementação.Em relação a VLAs versus um vetor
Você considerou que um vetor pode tirar proveito dos próprios VLAs. Sem os VLAs, o vetor precisa especificar certas "escalas" de matrizes, por exemplo, 10, 100, 10000 para armazenamento, para que você aloque uma matriz de 10000 itens para armazenar 101 itens. Com os VLAs, se você redimensionar para 200, o algoritmo pode assumir que você precisará apenas de 200 e pode alocar uma matriz de 200 itens. Ou pode alocar um buffer de dizer n * 1,5.
De qualquer forma, eu argumentaria que, se você souber quantos itens precisará em tempo de execução, um VLA terá melhor desempenho (como demonstrado pelo benchmark de Mats). O que ele demonstrou foi uma iteração simples de duas passagens. Pense em simulações de monte carlo, onde amostras aleatórias são coletadas repetidamente, ou manipulação de imagem (como filtros do Photoshop), onde os cálculos são feitos em cada elemento várias vezes e, possivelmente, cada cálculo em cada elemento envolve a observação de vizinhos.
Esse ponteiro extra salta do vetor para sua matriz interna.
Respondendo à pergunta principal
Mas quando você fala sobre o uso de uma estrutura alocada dinamicamente como um LinkedList, não há comparação. Uma matriz fornece acesso direto usando aritmética de ponteiro para seus elementos. Usando uma lista vinculada, você precisa percorrer os nós para chegar a um elemento específico. Portanto, o VLA ganha as mãos nesse cenário.De acordo com esta resposta , ele é dependente da arquitetura, mas em alguns casos o acesso à memória na pilha será mais rápido devido à disponibilidade da pilha no cache. Com um grande número de elementos, isso pode ser negado (potencialmente a causa dos retornos decrescentes que Mats viu em seus benchmarks). No entanto, vale a pena notar que os tamanhos de cache estão crescendo significativamente e você potencialmente verá mais esse número crescer de acordo.
fonte
std::vector
necessidade de escalas de matrizes? Por que precisaria de espaço para 10 mil elementos quando precisa apenas de 101? Além disso, a pergunta nunca menciona listas vinculadas, por isso não sei de onde você tirou isso. Finalmente, os VLAs no C99 são alocados à pilha; eles são uma forma padrão dealloca()
. Qualquer coisa que exija armazenamento em heap (permanece após a função retornar) ou arealloc()
(a matriz é redimensionada) proibiria os VLAs de qualquer maneira.O motivo para usar um VLA é principalmente o desempenho. É um erro desconsiderar o exemplo do wiki como tendo apenas uma diferença "irrelevante". Eu posso ver facilmente casos em que exatamente esse código poderia ter uma grande diferença, por exemplo, se essa função fosse chamada em um loop apertado, onde
read_val
havia uma função de E / S que retornava muito rapidamente em algum tipo de sistema em que a velocidade era crítica.De fato, na maioria dos lugares onde os VLAs são usados dessa maneira, eles não substituem as chamadas de heap, mas substituem algo como:
A questão de qualquer declaração local é que ela é extremamente rápida. A linha
float vals[n]
geralmente requer apenas algumas instruções do processador (talvez apenas uma). Simplesmente adiciona o valorn
ao ponteiro da pilha.Por outro lado, uma alocação de heap requer caminhar uma estrutura de dados para encontrar uma área livre. O tempo é provavelmente uma ordem de magnitude mais longa, mesmo nos casos mais afortunados. (Ou seja, apenas o ato de colocar
n
na pilha e chamarmalloc
é provavelmente de 5 a 10 instruções.) Provavelmente muito pior se houver uma quantidade razoável de dados no heap. Não me surpreenderia ver um caso demalloc
100x a 1000x mais lento em um programa real.Obviamente, você também terá algum impacto no desempenho com a correspondência
free
, provavelmente semelhante em magnitude àmalloc
chamada.Além disso, há o problema da fragmentação da memória. Muitas pequenas alocações tendem a fragmentar a pilha. Amontoados fragmentados desperdiçam memória e aumentam o tempo necessário para alocar memória.
fonte
int vla[n]; if(test()) { struct LargeStruct s; int i; }
deslocamento da pilha des
não será conhecido no momento da compilação, e também é duvidoso que o compilador mova o armazenamentoi
fora do escopo interno para o deslocamento da pilha fixo. Portanto, código de máquina extra é necessário porque a indireção e isso também podem consumir registros, importantes no hardware do PC. Se você quiser código de exemplo com saída de montagem compilador incluído, para fazer uma pergunta separada;)s
ei
quando a função é inserida, antestest
é chamada ouvla
é alocada, como alocaçõess
ei
sem efeitos colaterais. (E, de fato,i
pode até ser colocado em um registro, o que significa que não há "alocação"). Não há garantias do compilador para a ordem das alocações na pilha, ou mesmo que a pilha seja usada.