Valarray C ++ vs. vetor

Eu gosto muito de vetores. Eles são bacanas e rápidos. Mas eu sei que essa coisa chamada valarray existe. Por que eu usaria um valarray em vez de um vetor? Eu sei que os valarrays têm algum açúcar sintático, mas além disso, quando são úteis?

Valarrays (arrays de valor) são destinados a trazer um pouco da velocidade do Fortran para o C ++. Você não faria um valarray de pointers para que o compilador pudesse fazer suposições sobre o código e otimizá-lo melhor. (A principal razão pela qual o Fortran é tão rápido é que não há nenhum tipo de ponteiro, portanto, não pode haver aliasing de ponteiro.)

Valarrays também tem classs que permitem dividi-los de maneira razoavelmente fácil, embora essa parte do padrão possa usar um pouco mais de trabalho. Redimensioná-los é destrutivo e eles não têm iteradores.

Então, se são números com os quais você está trabalhando e conveniência não é tão importante, use valarrays. Caso contrário, os vetores são muito mais convenientes.

Valarray é uma espécie de órfão que nasceu no lugar errado na hora errada. É uma tentativa de otimização, bastante especificamente para as máquinas que foram usadas para matemática pesada quando foi escrita – especificamente, processadores vetoriais como o Crays.

Para um processador de vetor, o que você geralmente queria era aplicar uma única operação a uma matriz inteira, depois aplicar a próxima operação a toda a matriz, e assim por diante, até ter feito tudo o que precisava fazer.

A menos que você esteja lidando com matrizes relativamente pequenas, no entanto, isso tende a funcionar mal com o armazenamento em cache. Na maioria das máquinas modernas, o que você geralmente preferiria (na medida do possível) seria carregar parte do array, fazer todas as operações nele, depois passar para a próxima parte do array.

Valarray também deve eliminar qualquer possibilidade de aliasing, que (pelo menos teoricamente) permite que o compilador melhore a velocidade, porque é mais livre armazenar valores em registradores. Na realidade, no entanto, não tenho certeza de que qualquer implementação real aproveite isso em qualquer grau significativo. Eu suspeito que é um tipo de problema de galinha e ovo – sem o suporte do compilador, ele não se tornou popular, e desde que não seja popular, ninguém vai se dar ao trabalho de trabalhar em seu compilador para suportá-lo.

Há também uma série desconcertante (literalmente) de classs auxiliares para usar com o valarray. Você obtém slice, array_fray, gslice e gslice_array para brincar com partes de um valarray e fazê-lo agir como um array multidimensional. Você também recebe mask_array para “mascarar” uma operação (por exemplo, adicionar itens em x a y, mas apenas nas posições em que z é diferente de zero). Para fazer um uso mais do que trivial de valarray, você tem que aprender muito sobre essas classs auxiliares, algumas das quais são bastante complexas e nenhuma delas parece (pelo menos para mim) muito bem documentada.

Resumindo: embora tenha momentos de brilho, e possa fazer algumas coisas de forma muito precisa, há também algumas razões muito boas que são (e quase certamente permanecerão) obscuras.

Edit (oito anos depois, em 2017): Algumas das anteriores se tornaram obsoletas, pelo menos em algum grau. Por exemplo, a Intel implementou uma versão otimizada do valarray para seu compilador. Ele usa as Primitivas Integradas de Desempenho da Intel (Intel IPP) para melhorar o desempenho. Embora a melhoria exata do desempenho indubitavelmente varie, um teste rápido com código simples mostra uma melhora na velocidade de 2: 1, comparado ao código idêntico compilado com a implementação “padrão” do valarray .

Portanto, embora eu não esteja totalmente convencido de que os programadores de C ++ começarão a usar valarray em grande número, há algumas circunstâncias em que ele pode fornecer uma melhoria de velocidade.

Durante a padronização do C ++ 98, o valarray foi projetado para permitir algum tipo de cálculos matemáticos rápidos. No entanto, por volta dessa época, Todd Veldhuizen inventou templates de expressão e criou blitz ++ , e técnicas similares de template-meta foram inventadas, o que tornou as valarrays muito obsoletas antes mesmo de o padrão ser lançado. O IIRC, o (s) proponente (s) original (ais) de Valarray, abandonou-o na metade da padronização, o que (se for verdade) também não o ajudou.

ISTR que a principal razão pela qual não foi removido do padrão é que ninguém teve tempo para avaliar o problema completamente e escrever uma proposta para removê-lo.

Por favor, tenha em mente, no entanto, que tudo isso é vagamente lembrado por boatos. Tome isso com um grão de sal e espere que alguém corrija ou confirme isso.

Eu sei que os valarrays têm algum açúcar sintático

Eu tenho que dizer que eu não acho que o std::valarrays tenha muito a ver com o açúcar sintático. A syntax é diferente, mas eu não chamaria a diferença de “açúcar”. A API é estranha. A seção sobre std::valarray em The C ++ Programming Language menciona essa API incomum e o fato de que, como é esperado que std::valarray s sejam altamente otimizados, qualquer mensagem de erro obtida durante o uso provavelmente não será intuitiva.

Por curiosidade, há cerca de um ano coloquei std::valarray contra std::vector . Eu não tenho mais o código ou os resultados precisos (embora não seja difícil escrever o seu próprio). Usando o GCC , obtive um pequeno benefício de desempenho ao usar std::valarray para matemática simples, mas não para minhas implementações calcularem o desvio padrão (e, é claro, o desvio padrão não é tão complexo, na medida em que a matemática vai). Eu suspeito que as operações em cada item em um grande std::vector jogar melhor com caches que operações em std::valarray s. ( OBSERVE , seguindo o conselho do musiphil , consegui obter um desempenho quase idêntico do vector e valarray ).

No final, decidi usar std::vector enquanto prestava muita atenção em coisas como alocação de memory e criação temporária de objects.


Ambos std::vector e std::valarray armazenam os dados em um bloco contíguo. No entanto, eles acessam esses dados usando padrões diferentes e, mais importante, a API para std::valarray incentiva padrões de access diferentes da API para std::vector .

Para o exemplo do desvio padrão, em uma etapa específica, eu precisava encontrar a média da coleção e a diferença entre o valor de cada elemento e a média.

Para o std::valarray , fiz algo como:

 std::valarray original_values = ... // obviously I put something here double mean = original_values.sum() / original_values.size(); std::valarray temp(mean, original_values.size()); std::valarray differences_from_mean = original_values - temp; 

Eu posso ter sido mais inteligente com std::slice ou std::gslice . Já faz mais de cinco anos agora.

Para std::vector , fiz algo ao longo das linhas de:

 std::vector original_values = ... // obviously, I put something here double mean = std::accumulate(original_values.begin(), original_values.end(), 0.0) / original_values.size(); std::vector differences_from_mean; differences_from_mean.reserve(original_values.size()); std::transform(original_values.begin(), original_values.end(), std::back_inserter(differences_from_mean), std::bind1st(std::minus(), mean)); 

Hoje eu certamente escreveria isso de forma diferente. Se nada mais, eu aproveitaria o C ++ 11 lambdas.

É óbvio que esses dois trechos de código fazem coisas diferentes. Por um lado, o exemplo std::vector não faz uma coleção intermediária como o exemplo std::valarray . No entanto, acho justo compará-las porque as diferenças estão ligadas às diferenças entre std::vector e std::valarray .

Quando escrevi essa resposta, suspeitei que subtrair o valor de elementos de dois std::valarray s (última linha no exemplo std::valarray ) seria menos amigável para o cache do que a linha correspondente no exemplo std::vector ( que acontece também ser a última linha).

Acontece, no entanto, que

 std::valarray original_values = ... // obviously I put something here double mean = original_values.sum() / original_values.size(); std::valarray differences_from_mean = original_values - mean; 

Faz o mesmo que o exemplo std::vector , e tem desempenho quase idêntico. No final, a questão é qual API você prefere.

Valarray deveria permitir que alguma qualidade de processamento de vetores de Fortran fosse eliminada em C ++. De alguma forma, o suporte necessário ao compilador nunca aconteceu.

Os livros de Josuttis contêm alguns comentários interessantes (um tanto depreciativos) sobre valarray ( aqui e aqui ).

No entanto, a Intel agora parece estar revisando o valarray em seus lançamentos recentes do compilador (por exemplo, veja o slide 9 ); este é um desenvolvimento interessante, dado que o seu conjunto de instruções SIMD SSE de 4 vias está prestes a ser acompanhado por instruções 8-way AVX e 16-way Larrabee e no interesse da portabilidade, será muito melhor codificar com uma abstracção como valarray do que (digamos) intrínsecos.

O padrão C ++ 11 diz:

As classs de array valarray são definidas como livres de certas formas de aliasing, permitindo assim que as operações nessas classs sejam otimizadas.

Veja C ++ 11 26.6.1-2.

Eu encontrei um bom uso para valarray. É usar valarray apenas como matrizes numpy.

 auto x = linspace(0, 2 * 3.14, 100); plot(x, sin(x) + sin(3.f * x) / 3.f + sin(5.f * x) / 5.f); 

insira a descrição da imagem aqui

Podemos implementar acima com valarray.

 valarray linspace(float start, float stop, int size) { valarray v(size); for(int i=0; i arange(float start, float step, float stop) { int size = (stop - start) / step; valarray v(size); for(int i=0; i& x, const valarray& y) { int sz = x.size(); assert(sz == y.size()); int bytes = sz * sizeof(float) * 2; const char* name = "plot1"; int shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666); ftruncate(shm_fd, bytes); float* ptr = (float*)mmap(0, bytes, PROT_WRITE, MAP_SHARED, shm_fd, 0); for(int i=0; i 

Além disso, precisamos de script python.

 import sys, posix_ipc, os, struct import matplotlib.pyplot as plt sz = int(sys.argv[1]) f = posix_ipc.SharedMemory("plot1") x = [0] * sz y = [0] * sz for i in range(sz): x[i], y[i] = struct.unpack('ff', os.read(f.fd, 8)) os.close(f.fd) plt.plot(x, y) plt.show()