2009-10-21 27 views
130

Me gustan mucho los vectores. Son hábiles y rápidos. Pero sé que esta cosa llamada valarray existe. ¿Por qué usaría un valarray en lugar de un vector? Sé que los valarrays tienen algo de azúcar sintáctico, pero aparte de eso, ¿cuándo son útiles?C++ valarray vs. vector

+2

Sólo estaba reflexionando sobre esto el otro día, también. Hasta donde yo sé, es realmente solo como un vector matemático especializado. – GManNickG

+0

¿Valarray no hace plantillas de expresión? –

+0

Físico Ulrich Mutze proporciona un caso de uso para 'valarray' [aquí] (http://goo.gl/QVDJKq) y [aquí] (http://goo.gl/ja7q8d) – lifebalance

Respuesta

60

Valarrays (matrices de valores) están destinadas a traer algo de la velocidad de Fortran a C++. No haría una avalancha de punteros para que el compilador pueda hacer suposiciones sobre el código y optimizarlo mejor. (La razón principal por la que Fortran es tan rápido es que no hay ningún tipo de puntero, por lo que no puede haber alias de puntero.)

Valarrays también tienen clases que le permiten dividirlas de una manera razonablemente fácil, aunque esa parte del el estándar podría usar un poco más de trabajo. Cambiar el tamaño de ellos es destructivo y carecen de iteradores.

Por lo tanto, si se trata de números con los que está trabajando y la conveniencia no es tan importante, use valarrays. De lo contrario, los vectores son mucho más convenientes.

+13

Fortran ha tenido punteros desde Fortran 90 – user2023370

+6

No están diseñados para evitar punteros. C++ 11 define begin() y end() en valarray que les devuelve iteradores –

+3

@ user2023370: es por eso que muchos usuarios de Fortran prefieren Fortran 77. :) Las plantillas de expresión – Michael

33

Durante la estandarización de C++ 98, valarray fue diseñado para permitir algún tipo de cálculos matemáticos rápidos. Sin embargo, en esa época, Todd Veldhuizen inventó plantillas de expresión y creó blitz++, y se inventaron técnicas similares de plantilla-meta, lo que hizo que las valarrayas quedaran bastante obsoletas incluso antes de que se lanzara el estándar. IIRC, el proponente (s) original (es) de valarray lo abandonó a la mitad de la estandarización, lo cual (si es verdad) tampoco lo ayudó.

ISTR que la razón principal por la que no se eliminó del estándar es que nadie se tomó el tiempo para evaluar el problema a fondo y escribir una propuesta para eliminarlo.

Tenga en cuenta, sin embargo, que todo esto es un rumor vagamente recordado. Toma esto con un grano de sal y espera que alguien corrija o confirme esto.

+0

también se pueden atribuir a Vandevoorde, ¿no? –

+0

@Nikos: No es que yo sepa. Sin embargo, podría estar equivocado. ¿Qué tienes a favor de esa lectura? – sbi

+0

se menciona en el libro "Plantillas C++ - La guía completa", creo que generalmente se acepta que ambos los inventaron [independientemente] (http://collaboration.cmc.ec.gc.ca/science/rpn/biblio/ddj/Website/articles/CUJ/2003/0306/cuj0306becker /). –

22

se suponía que valarray permitía que algo de la bondad del procesamiento de vectores FORTRAN se frotara en C++. De alguna manera, el soporte necesario para el compilador nunca sucedió realmente.

Los libros de Josuttis contienen algunos comentarios interesantes (un poco despectivos) sobre valarray (here y here).

Sin embargo, ahora Intel parece estar revisando valarray en sus recientes versiones del compilador (por ejemplo, vea slide 9); esto es un desarrollo interesante dado que su conjunto de instrucciones SIMD SSE de 4 vías está a punto de ser acompañado por AVX de 8 vías y 16 instrucciones de Larrabee de 16 vías y en interés de la portabilidad será mucho mejor codificar con una abstracción como valarray que (digamos) intrínsecos.

116

valarray es una especie de huérfano que nació en el lugar equivocado en el momento equivocado. Es un intento de optimización, bastante específicamente para las máquinas que se usaron para las operaciones pesadas cuando se escribió, específicamente procesadores vectoriales como los Crays.

Para un procesador vectorial, lo que generalmente quería hacer era aplicar una sola operación a una matriz completa, luego aplicar la siguiente operación a la matriz completa, y así sucesivamente hasta que hubiera hecho todo lo que necesitaba hacer.

A menos que se trate de arreglos bastante pequeños, sin embargo, eso tiende a funcionar mal con el almacenamiento en caché. En la mayoría de las máquinas modernas, lo que generalmente preferirías (en la medida de lo posible) sería cargar parte de la matriz, hacer todas las operaciones en ella, y luego pasar a la siguiente parte de la matriz.

valarray También se supone que para eliminar cualquier posibilidad de representaciones falsas, que (al menos en teoría) permite que el compilador de mejorar la velocidad porque es más libre para almacenar los valores en los registros. En realidad, sin embargo, no estoy del todo seguro de que una implementación real tome ventaja de esto en un grado significativo. Sospecho que es más bien un problema de gallina y huevo: sin el apoyo del compilador no se hizo popular, y mientras no sea popular, nadie se tomará la molestia de trabajar en su compilador para soportarlo.

También hay una desconcertante variedad (literalmente) de las clases auxiliares para usar con valarray. Obtienes slice, slice_array, gslice y gslice_array para jugar con piezas de un valarray y hacerlo actuar como una matriz multidimensional. También obtiene mask_array para "enmascarar" una operación (por ejemplo, agregar elementos en xa y, pero solo en las posiciones donde z es distinto de cero). Para hacer un uso más que trivial de valarray, tienes que aprender mucho sobre estas clases auxiliares, algunas de las cuales son bastante complejas y ninguna de ellas parece (al menos para mí) muy bien documentada.

Conclusión: si bien tiene momentos de brillantez y puede hacer algunas cosas bastante bien, también hay algunas muy buenas razones por las que es (y seguramente seguirá siendo) oscuras.

Editar (ocho años después, en 2017): Un poco de lo anterior, ha quedado obsoleto a por lo menos algún grado. Por ejemplo, Intel ha implementado una versión optimizada de valarray para su compilador. Utiliza Intel Integrated Performance Primitives (Intel IPP) para mejorar el rendimiento. Aunque la mejora de rendimiento exacta indudablemente varía, una prueba rápida con código simple muestra una mejora de velocidad de 2: 1, en comparación con el código idéntico compilado con la implementación "estándar" de valarray.

Así que, aunque no estoy del todo convencido de que los programadores de C++ empiecen a usar valarray en grandes cantidades, hay al menos algunas circunstancias en las que puede proporcionar una mejora de velocidad.

+0

¿Está específicamente prohibido almacenar tipos de objetos arbitrarios dentro de valarray? – Mehrdad

+4

@Mehrdad: Sí, hay una lista (bastante larga) de restricciones en [Numéricos.Requisitos]. Solo por un par de ejemplos, todas las clases abstractas y excepciones están prohibidas. También requiere la equivalencia entre (por ejemplo) la construcción de copias y una secuencia de construcción predeterminada seguida de la asignación. –

+0

@JerryCoffin sheesh eso da miedo. prometemos que no lo usaremos. –

23

sé valarrays tienen un poco de azúcar sintáctica

tengo que decir que no creo std::valarrays tienen mucho en forma de azúcar sintáctico. La sintaxis es diferente, pero yo no llamaría a la diferencia "azúcar". La API es extraña. La sección en std::valarray s en El lenguaje de programación C++ menciona esta inusual API y el hecho de que desde std::valarray s se espera que sean altamente optimizados, los mensajes de error que reciba al usarlos probablemente no sean intuitivos.

Por curiosidad, hace aproximadamente un año que puche std::valarray contra std::vector. Ya no tengo el código ni los resultados precisos (aunque no debería ser difícil escribir el suyo). El uso de GCC me hice consiguen un pequeño beneficio en el rendimiento cuando se utiliza std::valarray de operaciones matemáticas sencillas, pero no para mis implementaciones para calcular la desviación estándar (y, por supuesto, la desviación estándar no es tan compleja, en lo que va de matemáticas). Sospecho que las operaciones en cada elemento de un gran juego std::vector mejor con cachés que las operaciones en std::valarray s. (NOTA, siguiendo las recomendaciones de musiphil, he logrado obtener un rendimiento casi idéntico de vector y valarray).

Al final, decidí usar std::vector prestando mucha atención a cosas como la asignación de memoria y la creación de objetos temporales.


Tanto std::vector y std::valarray almacenar los datos en un bloque contiguo. Sin embargo, acceden a esos datos utilizando diferentes patrones y, lo que es más importante, la API para std::valarray alienta diferentes patrones de acceso que la API para std::vector.

Para el ejemplo de desviación estándar, en un paso particular necesitaba encontrar la media de la colección y la diferencia entre el valor de cada elemento y la media.

Para el std::valarray, hice algo como:

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

que pude haber sido más inteligente con std::slice o std::gslice. Han pasado más de cinco años.

Para std::vector, hice algo a lo largo de las líneas de:

std::vector<double> original_values = ... // obviously, I put something here 
double mean = std::accumulate(original_values.begin(), original_values.end(), 0.0)/original_values.size(); 

std::vector<double> 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<double>(), mean)); 

Hoy en día, sin duda me gustaría escribir que de otra manera. Si nada más, aprovecharía C++ 11 lambdas.

Es obvio que estos dos fragmentos de código hacen cosas diferentes. Por ejemplo, el ejemplo std::vector no hace una colección intermedia como el ejemplo std::valarray. Sin embargo, creo que es justo compararlos porque las diferencias están ligadas a las diferencias entre std::vector y std::valarray.

Cuando escribí esta respuesta, yo sospechaba que restar el valor de los elementos a partir de dos std::valarray s (última línea del ejemplo std::valarray) sería menos caché de usar que la línea correspondiente en el ejemplo std::vector (que pasa a ser también la última línea).

Resulta, sin embargo, que

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

hace lo mismo que el ejemplo std::vector, y tiene un rendimiento casi idéntico. Al final, la pregunta es qué API prefiere.

+0

No puedo pensar en ninguna razón por la cual un 'std :: vector' jugaría mejor con caches que un' std :: valarray'; ambos asignan un solo bloque contiguo de memoria para sus elementos. – musiphil

+1

@musiphil Mi respuesta fue demasiado larga para un comentario, así que actualicé la respuesta. –

+0

Gracias por su respuesta actualizada. – musiphil

7

El C++ 11 estándar dice:

clases de matriz

El valarray se definen para ser libre de ciertas formas de aliasing, permitiendo así que las operaciones en estas clases para ser optimizados.

Ver C++ 11 26.6.1-2.

+0

Como supongo que el estándar define qué formularios, ¿puede citarlos? Además, ¿se implementan utilizando trucos de codificación o son excepciones basadas en el compilador a las reglas de alias en otro lugar del lenguaje? –

6

Encontré un buen uso para valarray. Es usar valarray como matrices 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); 

enter image description here

Podemos implementar anterior con valarray.

valarray<float> linspace(float start, float stop, int size) 
{ 
    valarray<float> v(size); 
    for(int i=0; i<size; i++) v[i] = start + i * (stop-start)/size; 
    return v; 
} 

std::valarray<float> arange(float start, float step, float stop) 
{ 
    int size = (stop - start)/step; 
    valarray<float> v(size); 
    for(int i=0; i<size; i++) v[i] = start + step * i; 
    return v; 
} 

string psstm(string command) 
{//return system call output as string 
    string s; 
    char tmp[1000]; 
    FILE* f = popen(command.c_str(), "r"); 
    while(fgets(tmp, sizeof(tmp), f)) s += tmp; 
    pclose(f); 
    return s; 
} 

string plot(const valarray<float>& x, const valarray<float>& 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<sz; i++) { 
     *ptr++ = x[i]; 
     *ptr++ = y[i]; 
    } 

    string command = "python plot.py "; 
    string s = psstm(command + to_string(sz)); 
    shm_unlink(name); 
    return s; 
} 

Además, necesitamos la secuencia de comandos 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()