Vamos a proseguir con las comparativas que iniciamos en WinAPI vs C en donde si recordáis comparábamos las funciones de la RTL de los compiladores de Visual C++ 2017 y C++ Builder 10.2.3 contra las funciones de la API de Windows.

Hoy vamos a centrarnos en Visual C++, y lo que procederemos será a comparar el rendimiento de las diferentes formas que permiten rellenar un bloque de memoria con un valor determinado. Vamos a usar FillMemory (API de Windows); memset (RTL de C/C++); __stosd (intrínseca de Visual C++); implementación en C puro, e implementación en ensamblador puro.

Los resultados inicialmente han sido previsibles, la implementación en C++, pese a usar 32 bits se ha mostrado notablemente más lenta. En un segundo nivel la API de Windows demuestra su rendimiento algo peor como ya vimos. Las otras implementaciones son muy similares.

Tiempo (ms)
FillMemory()5.187
FillMemory()4.609
__stosd4.563
C++38.031
Ensamblador4.610



Sin embargo si probamos lo mismo en modo Release, y con todas las optimizaciones activadas, las cifras cambian notablemente:

Tiempo (ms)
FillMemory()4.500
FillMemory()4.344
__stosd4.328
C++4.328
Ensamblador4.313

Todas las implementaciones tienen una velocidad de ejecución similar, incluyendo la versión en C, de manera que en proceso tan simples, el compilador es suficientemente listo como para generar el mejor código posible.

En las pruebas no he entrado en más combinaciones, por ejemplo usando movsb que pese a funcionar byte a byte, debido a las optimizaciones de Intel está más optimizado. Sí que os puedo decir que el beneficio marginal estaba en menos de 5%.

Podéis revisar el código fuente completo a continuación, o descargarlo junto al ejecutable aquí (64 Kb. en formato ZIP).

// -----------------------------------------------------------------------------
#pragma hdrstop
 
 
// -----------------------------------------------------------------------------
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <intrin.h>
#include <memory.h>
#include <Shlwapi.h>
 
 
 
// -----------------------------------------------------------------------------
#define KI_ITER 1000
#define KI_SIZE 64*1024*1024L
 
 
// -----------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
{
	unsigned long lStart, lStop;
	char *acBuffer = new char[KI_SIZE + 16];
 
 
	//FillMemory
	lStart = GetTickCount();
	for (unsigned int iCount = 0; iCount < KI_ITER; iCount++)
	{
		FillMemory(acBuffer, KI_SIZE, iCount);
	}
	lStop = GetTickCount();
	_tprintf(_T("FillMemory(): %ld ms\n"), lStop - lStart);
 
 
	//memset
	lStart = GetTickCount();
	for (unsigned int iCount = 0; iCount < KI_ITER; iCount++)
	{
		memset(acBuffer, iCount, KI_SIZE);
	}
	lStop = GetTickCount();
	_tprintf(_T("memset(): %ld ms\n"), lStop - lStart);
 
 
	//__stosd
	lStart = GetTickCount();
	for (unsigned int iCount = 0; iCount < KI_ITER; iCount++)
	{
		__stosd((unsigned long *) acBuffer, iCount, KI_SIZE / 4);
	}
	lStop = GetTickCount();
	_tprintf(_T("__stosd(): %ld ms\n"), lStop - lStart);
 
	//C
	lStart = GetTickCount();
	for (unsigned int iCount = 0; iCount < KI_ITER; iCount++)
	{
		for (unsigned int iC = 0; iC < KI_SIZE / 4; iC++)
		{
			((unsigned long *) acBuffer)[iC] = iCount;
		}
	}
	lStop = GetTickCount();
	_tprintf(_T("C: %ld ms\n"), lStop - lStart);
 
 
 
	//asm
	lStart = GetTickCount();
	_asm
	{
		mov edx, KI_ITER
 
		stosd_repeat:
		mov eax, edx
		mov ecx, KI_SIZE / 4
		mov edi, acBuffer
		rep stosd
		dec edx
		jnz stosd_repeat
	}
	lStop = GetTickCount();
	_tprintf(_T("_asm: %ld ms\n"), lStop - lStart);
 
 
	delete[] acBuffer;
 
 
	getchar();
	return(0);
}
// -----------------------------------------------------------------------------