C++ valarray vs. vector
stl stdvector (7)
Sé que los valarrays tienen algo de azúcar sintáctica
Debo decir que no creo que std::valarrays
tenga mucho en cuanto a 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 sobre std::valarray
s en The C ++ Programming Language menciona esta inusual API y el hecho de que, dado que se espera que std::valarray
s esté altamente optimizado, cualquier mensaje de error que reciba al usarlo probablemente no será intuitivo.
Por curiosidad, hace aproximadamente un año puse 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). Usando GCC obtuve un pequeño beneficio de rendimiento cuando uso std::valarray
para matemáticas simples, pero no para que mis implementaciones calculen la desviación estándar (y, por supuesto, la desviación estándar no es tan compleja, en lo que respecta a las matemáticas). Sospecho que las operaciones en cada elemento de un ( NOTA , siguiendo el consejo de musiphil , he logrado obtener un rendimiento casi idéntico de std::vector
grande funcionan mejor con los cachés que las operaciones en std::valarray
s.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 temporal de objetos.
Tanto std::vector
como std::valarray
almacenan 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 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;
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 como:
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 ciertamente escribiría eso de manera diferente. Si nada más, aprovecharía C ++ 11 lambdas.
Es obvio que estos dos fragmentos de código hacen cosas diferentes. Por un lado, 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, sospeché que restar el valor de los elementos de dos std::valarray
s (última línea en el ejemplo std::valarray
) sería menos amigable con el caché 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.
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?
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ó las plantillas de expresión y creó blitz++ , y se inventaron técnicas similares de plantilla-meta, lo que hizo que las valarrayas quedaran obsoletas antes de que el estándar fuera lanzado. 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.
El estándar C ++ 11 dice:
Las clases de matriz valarray se definen como libres de ciertas formas de aliasing, lo que permite optimizar las operaciones en estas clases.
Ver C ++ 11 26.6.1-2.
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);
Podemos implementar arriba 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 una 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()
Valarrays (matrices de valores) tienen la intención de llevar 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).
Las Valarrays también tienen clases que te permiten cortarlas de una manera razonablemente fácil, aunque esa parte del estándar podría requerir un poco más de trabajo. Cambiar el tamaño de ellos es destructivo y carecen de iteradores.
Entonces, 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.
se suponía que valarray permitía que algo de bondad de procesamiento de vectores FORTRAN se rozara en C ++. De alguna manera, el soporte necesario para el compilador nunca sucedió realmente.
Los libros de Josuttis contienen algunos comentarios interesantes (algo despectivos) sobre valarray ( here y here ).
Sin embargo, Intel ahora parece estar revisando Valarray en sus recientes versiones del compilador (por ejemplo, ver diapositiva 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.
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 de vectores, 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 tenía que hacer.
A menos que se trate de matrices bastante pequeñas, 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.
También se supone que valarray elimina cualquier posibilidad de aliasing, lo que (al menos teóricamente) permite al compilador mejorar la velocidad porque es más libre de almacenar 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 serie desconcertante (literalmente) de 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 xay, 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.
En pocas palabras: a pesar de que tiene momentos de brillantez y puede hacer algunas cosas bastante bien, también hay algunas muy buenas razones por las cuales es (y casi seguro permanecerá) oscuras.
Editar (ocho años más tarde, en 2017): algunos de los anteriores se han vuelto obsoletos, al menos hasta cierto punto. 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 exacta del rendimiento 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
.
Entonces, aunque no estoy del todo convencido de que los programadores de C ++ estén comenzando a usar valarray
en grandes cantidades, hay al menos algunas circunstancias en las que puede proporcionar una mejora en la velocidad.