studio - Estado de la funcionalidad "memset" en C++ con compiladores modernos
visual studio installer (12)
Bueno, todo esto depende del dominio de tu problema y tus especificaciones, ¿has encontrado problemas de rendimiento, no has cumplido con la fecha límite y has señalado a memset como la raíz de todo mal? Si es esto, estás en el único caso en el que podrías considerar ajustar algunos memset.
Entonces, también debe tener en cuenta que el memset de todos modos variará en el hardware de la plataforma en la que se ejecuta, durante esos cinco años, ¿se ejecutará el software en la misma plataforma? En la misma arquitectura? Una vez que llegas a esa conclusión, puedes intentar ''enrollar tu propio'' memset, generalmente jugando con la alineación de los buffers, asegurándote de poner a cero los valores de 32 bits a la vez según lo que sea más eficiente en tu arquitectura.
Una vez me encontré con lo mismo para el memcmpt donde la alineación de arriba causó algunos problemas, bit típicamente esto no dará como resultado milagros, solo una pequeña mejora, si hay alguna. Si te estás perdiendo tus requisitos por una orden de mangitud, esto no te llevará más lejos.
Contexto:
Hace un tiempo, me encontré con este artículo DDJ 2001 por Alexandrescu: http://www.ddj.com/cpp/184403799
Se trata de comparar varias formas de inicializar un búfer con algún valor. Como lo que "memset" hace para valores de un solo byte. Comparó varias implementaciones (memcpy, explícito "for" loop, dispositivo de duff) y realmente no encontró el mejor candidato en todos los tamaños de conjunto de datos y todos los compiladores.
Citar:
Hay una comprensión muy profunda y triste detrás de todo esto. Estamos en 2001, el año de Spatial Odyssey. (...) Simplemente salga de la caja y míranos; después de 50 años, todavía no somos muy buenos para llenar y copiar la memoria.
Pregunta:
- ¿Alguien tiene información más reciente sobre este problema? ¿Las implementaciones recientes de GCC y Visual C ++ tienen un rendimiento significativamente mejor que hace 7 años?
- Estoy escribiendo código que tiene una vida útil de más de 5 años (probablemente más de 10 años) y que procesará el tamaño de los arreglos de unos pocos bytes a cientos de megabytes. No puedo asumir que mis elecciones ahora serán óptimas en 5 años. Que debería hacer:
- a) utilice el memset del sistema (o equivalente) y olvide el rendimiento óptimo o suponga que el tiempo de ejecución y el compilador manejarán esto por mí.
- b) punto de referencia de una vez por todas en varios tamaños de matriz y compiladores y cambiar en tiempo de ejecución entre varias rutinas.
- c) ejecute el benchmark en la inicialización del programa y cambie en tiempo de ejecución en base a datos precisos (?).
Editar: estoy trabajando en software de procesamiento de imágenes. ¡Mis elementos de matriz son POD y cada milisegundo cuenta!
Edit 2: Gracias por las primeras respuestas, aquí hay algunas informaciones adicionales:
- La inicialización del búfer puede representar el 20% -40% del tiempo de ejecución total de algunos algoritmos.
- La plataforma puede variar en los próximos 5 años o más, aunque se mantendrá en la categoría de "la CPU más rápida puede comprar de DELL". Los compiladores serán de alguna forma de GCC y Visual C ++. No hay material incrustado o arquitecturas exóticas en el radar
- Me gustaría saber de personas que tuvieron que actualizar su software cuando aparecieron MMX y SSE, ya que tendré que hacer lo mismo cuando esté disponible "SSE2015" ... :)
Depende de lo que estés haciendo. Si tiene un caso muy específico, a menudo puede superar ampliamente la libc del sistema (y / o el compilador en línea) de memset y memcpy.
Por ejemplo, para el programa en el que trabajo, escribí un memcpy y memset alineados con 16 bytes diseñados para tamaños de datos pequeños. La memcpy se hizo para múltiples de 16 tamaños superiores o iguales a 64 solamente (con datos alineados a 16), y memset se hizo solo para múltiples tamaños de 128. Estas restricciones me permitieron obtener una velocidad enorme, y como controlé la aplicación, pude adaptar las funciones específicamente a lo que se necesitaba, y también adaptar la aplicación para alinear todos los datos necesarios.
La memcpy se realizó a aproximadamente 8-9x la velocidad de la memcpy nativa de Windows, haciendo que una copia de 460 bytes se reduzca a solo 50 ciclos de reloj. El memset fue aproximadamente 2,5 veces más rápido, llenando una matriz de ceros de pila de manera extremadamente rápida.
Si estás interesado en estas funciones, se pueden encontrar aquí ; desplázate hasta alrededor de la línea 600 para memcpy y memset. Son bastante triviales. Tenga en cuenta que están diseñados para pequeños búferes que se supone que están en caché; si desea inicializar enormes cantidades de datos en la memoria al pasar por alto el caché, su problema puede ser más complejo.
El artículo DDJ reconoce que memset es la mejor respuesta, y mucho más rápido de lo que estaba tratando de lograr:
Hay algo sacrosanto en las funciones de manipulación de memoria de C: memset, memcpy y memcmp. Es probable que sean altamente optimizados por el proveedor del compilador, en la medida en que el compilador pueda detectar llamadas a estas funciones y reemplazarlas con instrucciones de ensamblador en línea, este es el caso de MSVC.
Entonces, si memset funciona para usted (es decir, se está inicializando con un solo byte), entonces úselo.
Si bien cada milisegundo puede contar, debe establecer qué porcentaje del tiempo de ejecución se pierde para configurar la memoria. Es probable que sea muy bajo (1 o 2%) dado que tiene un trabajo útil que hacer también. Dado que el esfuerzo de optimización probablemente tenga una tasa de rendimiento mucho mejor en otro lugar.
Memset / memcpy están escritos en su mayoría con un conjunto de instrucciones básicas en mente, y así pueden ser superados por rutinas SSE especializadas, que por otro lado imponen ciertas restricciones de alineación.
Pero para reducirlo a una lista:
- Para conjuntos de datos <= varios cientos de kilobytes, memcpy / memset se ejecuta más rápido que cualquier cosa que pueda simular.
- Para conjuntos de datos> megabytes, use una combinación de memcpy / memset para obtener la alineación y luego use sus propias rutinas / repliegue optimizados SSE para rutinas optimizadas de Intel, etc.
- Haga cumplir la alineación al inicio y use sus propias rutinas SSE.
Esta lista solo entra en juego para las cosas donde necesitas el rendimiento. Los conjuntos de datos demasiado pequeños / o una vez inicializados no justifican la molestia.
Aquí hay una implementación de memcpy de AMD, no puedo encontrar el artículo que describe el concepto detrás del código.
Puedes echar un vistazo a liboil, ellos (intentan) proporcionar una implementación diferente de la misma función y elegir el más rápido en la inicialización. Liboil tiene una licencia bastante liberal, por lo que puede tomar también para software propietario.
Si la memoria no es un problema, entonces crea un buffer estático del tamaño que necesitas, inicializado a tu valor (es). Hasta donde yo sé, estos dos compiladores están optimizando los compiladores, por lo que si usa un for-loop simple, el compilador debe generar los comandos ensambladores óptimos para copiar el búfer.
Si la memoria es un problema, use un búfer más pequeño y copie el mismo al tamaño de (..) desplazamientos en el nuevo búfer.
HTH
Si tiene que asignar su memoria e inicializarla, yo:
- Use calloc en lugar de malloc
- Cambie la mayor cantidad posible de mis valores predeterminados a cero (por ejemplo, deje que mi valor de enumeración predeterminado sea cero, o si el valor predeterminado de una variable booleana es ''verdadero'', almacénelo como valor inverso en la estructura)
La razón de esto es que Calloc inicia por cero la memoria por usted. Si bien esto implicará la sobrecarga para la reducción a cero de la memoria, es probable que la mayoría de los compiladores tengan esta rutina altamente optimizada, más optimizada que malloc / new con una llamada a memcpy.
Siempre elegiría un método de inicialización que sea parte del tiempo de ejecución o sistema operativo (memset) que estoy usando (peor caso elija uno que sea parte de una biblioteca que estoy usando).
Por qué: si está implementando su propia inicialización, podría terminar con una solución marginalmente mejor ahora, pero es probable que en un par de años el tiempo de ejecución haya mejorado. Y no quieres hacer el mismo trabajo que los chicos que mantienen el tiempo de ejecución.
Todo esto se mantiene si la mejora en el tiempo de ejecución es marginal. Si tiene una diferencia de un orden de magnitud entre memset y su propia inicialización, entonces tiene sentido tener su código en ejecución, pero realmente dudo de este caso.
d) Acepte que tratar de jugar "trucos mentales jedi" con la inicialización conducirá a más horas de programador perdidas que la diferencia acumulativa de milisegundos entre algún método oscuro pero rápido versus algo obvio y claro.
El año ya no es 2001. Desde entonces, han aparecido nuevas versiones de Visual Studio. Me he tomado el tiempo para estudiar el memset en esos. Usarán SSE para memset (si está disponible, por supuesto). Si su código anterior era correcto, estadísticamente , si ahora será más rápido. Pero podrías golpear una desafortunada esquina. Espero lo mismo de GCC, aunque no he estudiado el código. Es una mejora bastante obvia y un compilador de código abierto. Alguien habrá creado el parche.
El MASM Forum tiene muchos increíbles programadores de lenguaje ensamblador / aficionados que han superado completamente este problema hasta la muerte (eche un vistazo a través del Laboratorio). Los resultados fueron muy parecidos a la respuesta de Christopher: la SSE es increíble para buffers grandes, alineados, pero al descender eventualmente llegarás a un tamaño tan pequeño que un bucle for
es igual de rápido.
Como siempre ocurre con este tipo de preguntas, el problema está restringido por factores fuera de su control, es decir, el ancho de banda de la memoria. Y si el sistema operativo host decide comenzar a buscar la memoria, las cosas empeoran. En las plataformas Win32, la memoria está paginada y las páginas solo se asignan en el primer uso, lo que generará una gran pausa en cada límite de página mientras que el sistema operativo encuentra una página para usar (esto puede requerir que se envíe otra página de proceso al disco).
Sin embargo, este es el memset
más rápido que memset
haya escrito:
void memset (void *memory, size_t size, byte value)
{
}
No hacer algo siempre es la manera más rápida. ¿Hay alguna manera de escribir los algoritmos para evitar el memset
inicial? ¿Cuáles son los algoritmos que estás usando?