Suponiendo que tenemos un T myarray[100]
con T = int, unsigned int, long long int o unsigned long long int, cuál es la forma más rápida de restablecer todo su contenido a cero (no solo para la inicialización sino para restablecer el contenido varias veces en mi programa) ? ¿Quizás con memset?
La misma pregunta para una matriz dinámica como T *myarray = new T[100]
.
new
es C ++ ...memset
cuando C ++ está involucrado de alguna manera ... :)for
ciclo simple . Pero, sorprendentemente, puede hacerlo mucho peor si trata de ser inteligente.Respuestas:
memset
(from<string.h>
) es probablemente la forma estándar más rápida, ya que generalmente es una rutina escrita directamente en ensamblador y optimizada a mano.Por cierto, en C ++ la forma idiomática sería usar
std::fill
(from<algorithm>
):que puede optimizarse automáticamente en a
memset
; Estoy bastante seguro de que funcionará tan rápido comomemset
paraint
s, mientras que puede funcionar un poco peor para tipos más pequeños si el optimizador no es lo suficientemente inteligente. Aún así, en caso de duda, perfil.fuente
memset
estableciera un número entero en 0; no hubo una declaración específica de que todo-bits-cero sea una representación de0
. Una corrección técnica agregó dicha garantía, que se incluye en la norma ISO C de 2011. Creo que all-bits-zero es una representación válida de0
para todos los tipos de enteros en todas las implementaciones existentes de C y C ++, razón por la cual el comité pudo agregar ese requisito. (No existe una garantía similar para los tipos de puntero o punto flotante.)0
. (Con los bits de relleno, existe la posibilidad de que todos los bits cero puedan ser una representación de trampa). Pero en cualquier caso, se supone que el TC debe reconocer y reemplazar el texto defectuoso, por lo que a partir de 2004 deberíamos actuar como si C99 siempre contuviera este texto.int (*myarray)[N] = malloc(sizeof(*myarray));
.N
es, pero en la gran mayoría de los casos, si lo usómalloc
, solo lo sabía en tiempo de ejecución.Esta pregunta, aunque bastante antigua, necesita algunos puntos de referencia, ya que no pide la forma más idiomática, ni la forma en que se puede escribir en el menor número de líneas, sino la forma más rápida. Y es una tontería responder esa pregunta sin algunas pruebas reales. Así que comparé cuatro soluciones, memset versus std :: fill versus CERO de la respuesta de AnT versus una solución que hice usando intrínsecos AVX.
Tenga en cuenta que esta solución no es genérica, solo funciona con datos de 32 o 64 bits. Por favor comente si este código está haciendo algo incorrecto.
No afirmaré que este es el método más rápido, ya que no soy un experto en optimización de bajo nivel. Más bien es un ejemplo de una implementación dependiente de la arquitectura correcta que es más rápida que Memset.
Ahora, sobre los resultados. Calculé el rendimiento para matrices de tamaño 100 int y long long, asignadas tanto estática como dinámicamente, pero con la excepción de msvc, que eliminó el código muerto en matrices estáticas, los resultados fueron extremadamente comparables, por lo que mostraré solo el rendimiento de matriz dinámica. Las marcas de tiempo son ms para 1 millón de iteraciones, utilizando la función de reloj de baja precisión de time.h.
clang 3.8 (Usando la interfaz clang-cl, indicadores de optimización = / OX / arch: AVX / Oi / Ot)
gcc 5.1.0 (indicadores de optimización: -O3 -march = native -mtune = native -mavx):
msvc 2015 (indicadores de optimización: / OX / arch: AVX / Oi / Ot):
Hay muchas cosas interesantes sucediendo aquí: llvm matando a gcc, las típicas optimizaciones irregulares de MSVC (hace una eliminación impresionante de código muerto en matrices estáticas y luego tiene un rendimiento terrible para el relleno). Aunque mi implementación es significativamente más rápida, esto puede deberse solo a que reconoce que el borrado de bits tiene mucho menos sobrecarga que cualquier otra operación de configuración.
La implementación de Clang merece más atención, ya que es significativamente más rápida. Algunas pruebas adicionales muestran que su conjunto de memorias está de hecho especializado para memsets de cero o distintos de cero para una matriz de 400 bytes que son mucho más lentos (~ 220ms) y son comparables a los de gcc. Sin embargo, la configuración de memoria distinta de cero con una matriz de 800 bytes no hace ninguna diferencia de velocidad, lo que probablemente sea la razón por la que en ese caso, su configuración de memoria tiene un rendimiento peor que mi implementación: la especialización es solo para matrices pequeñas y el límite es de alrededor de 800 bytes. También tenga en cuenta que gcc 'fill' y 'ZERO' no optimizan memset (mirando el código generado), gcc simplemente está generando código con características de rendimiento idénticas.
Conclusión: memset no está realmente optimizado para esta tarea tan bien como la gente pretendería (de lo contrario, gcc, msvc y memset de llvm tendrían el mismo rendimiento). Si el rendimiento importa, entonces Memset no debería ser una solución final, especialmente para estos arreglos de tamaño medio incómodos, porque no está especializado en la eliminación de bits y no está optimizado a mano mejor que el compilador por sí solo.
fuente
a
caben en un registro. Luego, recorre todos los bloques de 32 bytes, que deberían sobrescribirse por completo usando aritmética de puntero ((float *)((a)+x)
). Los dos intrínsecos (comenzando con_mm256
) simplemente crean un registro de 32 bytes inicializado en cero y lo almacenan en el puntero actual. Estas son las primeras 3 líneas. El resto solo maneja todos los casos especiales en los que el último bloque de 32 bytes no debería sobrescribirse por completo. Es más rápido debido a la vectorización. - Espero que eso ayude.De
memset()
:Puede usar
sizeof(myarray)
simyarray
se conoce el tamaño de en tiempo de compilación. De lo contrario, si está utilizando una matriz de tamaño dinámico, como la obtenida a través demalloc
onew
, deberá realizar un seguimiento de la longitud.fuente
sizeof
siempre se evalúa en tiempo de compilación (y no se puede usar con VLA). En C99, puede ser una expresión en tiempo de ejecución en el caso de VLA.c
yc++
. Comenté la respuesta de Alex, que dice "Puede usar sizeof (myarray) si el tamaño de myarray se conoce en tiempo de compilación".Puede usar
memset
, pero solo porque nuestra selección de tipos está restringida a tipos integrales.En el caso general en C, tiene sentido implementar una macro
Esto le dará una funcionalidad similar a C ++ que le permitirá "restablecer a ceros" una serie de objetos de cualquier tipo sin tener que recurrir a hacks como
memset
. Básicamente, esta es una plantilla de función C análoga a C ++, excepto que debe especificar el argumento de tipo explícitamente.Además de eso, puede crear una "plantilla" para matrices no deterioradas
En su ejemplo, se aplicaría como
También vale la pena señalar que específicamente para objetos de tipos escalares se puede implementar una macro independiente del tipo
y
convirtiendo el ejemplo anterior en
fuente
;
después dewhile(0)
, para que uno pueda llamarZERO(a,n);
, +1 excelente respuestado{}while(0)
idioma requiere no;
en la definición macro. Fijo.Para una declaración estática, creo que podría usar:
Para la declaración dinámica sugiero la misma manera:
memset
fuente
zero(myarray);
es todo lo que necesita en C ++.Simplemente agregue esto a un encabezado:
fuente
zero
también es correcta para, por ejemplo,T=char[10]
como podría ser el caso cuando elarr
argumento es una matriz multidimensional, por ejemplochar arr[5][10]
.ARRAY_SIZE
macro, que da el tamaño incorrecto si se usa en una matriz multidimensional, quizás un mejor nombre seríaARRAY_DIM<n>_SIZE
.Aquí está la función que uso:
Puedes llamarlo así:
Arriba hay más forma de C ++ 11 que usar memset. También obtiene un error de tiempo de compilación si usa una matriz dinámica especificando el tamaño.
fuente