Whetstone es un pequeño benchmark científico diseñado en el Laboratorio Nacional de Física de Inglaterra. Se lo considera uno de los padres de los benchmarks sintéticos por ser el primero diseñado específicamente con ese fin. Hoy en día forma parte de muchos benchmarks actuales.
El Whetstone original fue diseñado en la década de 1960 por Brian Wichmann en el National Physical Laboratory, en Inglaterra, como un test para un compilador ALGOL 60 para una máquina hipotética. El sistema de compilación fue denominado Whetstone, localidad en la que fue diseñado, y el nombre parece haber quedado pegado luego al benchmark en sí mismo. El programa fue diseñado basándose en el estudio de 949 programas Algol '60, por lo que constituye un benchmark "sintético". Debido a que sus creadores se basaron en programas científicos de la época, este benchmark puede considerarse como científico. Su importancia histórica radica en que fue el primer programa diseñado para benchmarking.
La primera implementación práctica del Whetstone fue escrita por Harold Curnow en Fortran en 1972 (Curnow y Wichmann juntos publicaron un artículo sobre el benchmark Whetstone en 1976 para The Computer Journal). Sin embargo, luego fue traducido a varios lenguajes (Pascal, C, Fortran 77, Visual Basic, etc.). Fue diseñado para medir la velocidad de ejecución de una variedad de instrucciones de coma flotante (+, *, seno, cos, atan, sqrt, log, exp) en datos escalares o vectoriales, aunque también contiene algo de código de enteros, condicionales y llamadas a procedimientos. En líneas generales intenta estimar la velocidad de la CPU con la FPU.
Uno de los grandes problemas del Whetstone es que no cuenta con una versión oficial, por lo que hay que tener mucho cuidado en las comparaciones de que realmente se está usando el mismo tipo de programa, y sobre todo la misma cantidad de iteraciones por bucle.
En sus comienzos demostró ser una buena medida de rendimiento, sin embargo con el tiempo su funcionalidad se vio reducida por su alta sensibilidad a optimizadores. A finales de la década de 1980 y comienzos de la década de 1990 se reconoció que el Whetstone no funcionaría adecuadamente para medir el rendimiento de supercomputadoras con multiprocesadores paralelos. Sin embargo el Whetstone aún se utiliza ampliamente, porque provee una medida muy razonable de rendimiento de monoprocesadores de aritmética flotante. Uno de sus mayores usos actualmente es como parte de otros benchmarks (por ejemplo la primera versión del iCOMP de Intel o el Wintune).
Resultado:
Los resultados son provistos en MWIPS (Millones de Instrucciones Whetstone Por Segundo). El significado de la expresión "Instrucciones Whetstone" no queda claro salvo que uno examine cuidadosamente el código fuente. Sin embargo, se puede definir a una instrucción Whetstone como una instrucción de punto flotante promedio. Se calcula como (100 * CANTIDAD DE ITERACIONES * CANTIDAD DE WIPS POR ITERACIÓN / TIEMPO DE EJECUCIÓN).
Su código fuente es corto y relativamente fácil de entender, con una estructura limpia y que se explica por sí misma. El tiempo de ejecución es corto: 100 segundos (por diseño). Es muy preciso (tiene pequeñas variaciones en los resultados).
En el benchmark Whetstone, el código objeto que se itera es muy pequeño, cabiendo completamente en la caché interna de los procesadores modernos, por lo tanto manteniendo el pipeline de la FPU completo y a la FPU permanentemente ocupada. Esto es deseable porque el Whetstone hace exactamente lo que nosotros queremos que haga: medir de la FPU, no el acoplamiento de la CPU (caché externa/memoria principal), rendimiento de enteros o cualquier otra característica del sistema bajo prueba (sin embargo hay evidencia de que en los Pentium actuales hay interacción con la caché externa y la memoria principal debido a su complicado pipelining).
Las bibliotecas matemáticas usadas para las funciones trigonométricas y matemáticas trascendentales deberían ser compiladas en muy poco código ejecutable dado que todas las CPUs modernas tienen FPUs con estas funciones incorporadas.
El número de iteraciones es lo suficientemente grande como para que el costo por instrucciones extras sea despreciable. Además es poco utilizado para la Evaluación de Sistemas Informáticos en la UCAB.
El bucle principal del Whetstone se ejecuta en unos pocos milisegundos en una máquina moderna promedio, así que sus diseñadores resolvieron proveer un procedimiento de calibración que ejecutará primero 1 pasada, luego 5, luego 25, etc. hasta que la calibración lleve más de 2 segundos, y luego calcula un número de pasos extras para que resulte en aproximadamente un tiempo de ejecución de 100 segundos. Luego ejecutará pasadas extras para cada una de las 8 secciones del bucle principal, midiendo el tiempo de ejecución para cada una y calculará los MWIPS.
El Whetstone ejecuta 11 bucles cada uno de los cuales tiene un tipo distinto de instrucciones (identificadores simples, elementos de matrices, saltos condicionales, aritmética entera, funciones trigonométricas, llamadas a procedimiento, funciones matemáticas estándar raíz cuadrada, exponencial y logaritmo natural, matrices como parámetros, referencias a matrices). Al comenzar el programa lo que se hace es establecer la cantidad de veces que se iterará cada bucle, en otras palabras, se asigna un peso a cada tipo de instrucción o bucle. El problema de este enfoque es que cada bucle tiene su propio tiempo de ejecución, por lo que la relación entre número de iteraciones y peso no es tan directa. Finalmente se llama a cada uno de estos bucles, se imprimen sus resultados y se calcula el rendimiento en MWIPS.
El siguiente es el programa fuente en lenguaje Pascal.
PROGRAM WHETSTONE; CONST IM = 1.0; (* Cuántos millones de whetstones se ejecutarán *) T=0.499975; T1=0.50025; T2=2.0; TYPE ARGARRAY = ARRAY[1..4] OF REAL; VAR E1 : ARGARRAY; X,Y,Z,X1,X2,X3,X4 : REAL; I, J, K, L, N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11 : INTEGER; {$R-} {deshabilitar el chequeo de rango} PROCEDURE PA(VAR E:ARGARRAY); LABEL 1; VAR J : INTEGER; BEGIN J:=0; 1: E[1]:=(E[1]+E[2]+E[3]-E[4])*T; E[2]:=(E[1]+E[2]-E[3]+E[4])*T; E[3]:=(E[1]-E[2]+E[3]+E[4])*T; E[4]:=(-E[1]+E[2]+E[3]+E[4])/T2; J:=J+1; IF J<6 THEN GOTO 1 END; (* PROCEDURE PA*) PROCEDURE P0; BEGIN E1[J]:=E1[K]; E1[K]:=E1[L]; E1[L]:=E1[J] END; (* PROCEDURE P0 *) PROCEDURE P3(X,Y:REAL;VAR Z:REAL); BEGIN X:=T*(X+Y); Y:=T*(X+Y); Z:=(X+Y)/T2 END; (* PROCEDURE P3 *) PROCEDURE MODULE1; (* MODULE 1: SIMPLE IDENTIFIERS *) VAR I : INTEGER; BEGIN X1:=1.0; X2:=-1.0; X3:=-1.0; X4:=-1.0; FOR I:=1 TO N1 DO BEGIN X1:=(X1+X2+X3-X4)*T; X2:=(X1+X2-X3+X4)*T; X3:=(X1-X2+X3+X4)*T; X4:=(-X1+X2+X3+X4)*T END; END; (* MODULE 1 *) PROCEDURE MODULE2; (* MODULE 2: ARRAY ELEMENTS *) VAR I : INTEGER; BEGIN E1[1]:=1.0; E1[2]:=-1.0; E1[3]:=-1.0; E1[4]:=-1.0; FOR I:=1 TO N2 DO BEGIN E1[1]:=(E1[1]+E1[2]+E1[3]-E1[4])*T; E1[2]:=(E1[1]+E1[2]-E1[3]+E1[4])*T; E1[3]:=(E1[1]-E1[2]+E1[3]+E1[4])*T; E1[4]:=(-E1[1]+E1[2]+E1[3]+E1[4])*T END; END; (* MODULE 2 *) PROCEDURE MODULE4; (* MODULE 4: CONDITIONAL JUMPS *) VAR I : INTEGER; BEGIN J:=1; FOR I:=1 TO N4 DO BEGIN IF J=1 THEN J:=2 ELSE J:=3; IF J>1 THEN J:=0 ELSE J:=1; IF J<2 THEN J:=1 ELSE J:=0 END; END; (* MODULE 4 *) PROCEDURE MODULE6; (* INTEGER ARITHMETIC *) VAR I : INTEGER; BEGIN J:=1; K:=2; L:=3; FOR I:= 1 TO N6 DO BEGIN J:=J*(K-J)*(L-K); K:=L*K-(L-J)*K; L:=(L-K)*K+J; E1[L-1]:=J+K+L; E1[K-1]:=J*K*L END; END; (* MODULE 6 *) PROCEDURE MODULE7; (* MODULE 7: TRIG FUNCTIONS *) VAR I : INTEGER; TEMP : REAL; BEGIN X:=0.5; Y:=0.5; FOR I:=1 TO N7 DO BEGIN TEMP:=COS(X+Y)+COS(X-Y)-1.0; X:=T*ARCTAN(T2*SIN(X)*COS(X)/TEMP); TEMP:=COS(X+Y)+COS(X-Y)-1.0; Y:=T*ARCTAN(T2*SIN(Y)*COS(Y)/TEMP); END; END; (* MODULE 7 *) PROCEDURE MODULE8; (* MODULE 8: PROCEDURE CALLS *) VAR I : INTEGER; BEGIN X:=1.0; Y:=1.0; Z:=1.0; FOR I:=1 TO N8 DO P3(X,Y,Z) END; (* MODULE 8 *) PROCEDURE MODULE10; (* MODULE 10: INTEGER ARTIHMETIC *) VAR I : INTEGER; BEGIN J:=2; K:=3; FOR I:=1 TO N10 DO BEGIN J:=J+K; K:=J+K; J:=K-J; K:=K-J-J END; END; (* MODULE 10 *) PROCEDURE MODULE11; (* MODULE 11: STANDARD FUNCTIONS *) VAR I : INTEGER; BEGIN X:=0.75; FOR I:=1 TO N11 DO X:=SQRT(EXP(LN(X)/T1)); END; (* MODULE 11 *) PROCEDURE POUT(VAR N,J,K:INTEGER; VAR X1,X2,X3,X4:REAL); BEGIN WRITE(N:7,J:6,K:6); WRITELN(X1:11:3,X2:12:3,X3:12:3,X4:12:3); END; (* PROCEDURE POUT *) BEGIN (* START WHETSTONE *) I := TRUNC( 10.0 * IM); N1:=0; N2:=12*I; N3:=14*I; N4:=345*I; N5:=0; N6:=210*I; N7:=32*I; N8:=899*I; N9:=616*I; N10:=0; N11:=93*I; (* MODULAR PROGRAMMING IS USED TO REDUCE THE LENGTH OF MAIN CODE *) MODULE1; (* SIMPLE IDENTIFIERS *) POUT(N1,N1,N1,X1,X2,X3,X4); MODULE2; (* ARRAY ELEMENTS *) POUT(N2,N3,N2,E1[1],E1[2],E1[3],E1[4]); (* MODULE 3: ARRAY AS A PARAMETER *) FOR I:= 1 TO N3 DO PA(E1); POUT(N3,N2,N2,E1[1],E1[2],E1[3],E1[4]); (* END OF MODULE 3 *) MODULE4; (* CONDITIONAL JUMPS *) POUT(N4,J,J,X1,X2,X3,X4); MODULE6; (* INTEGER ARITHMETIC *) POUT(N6,J,K,E1[1],E1[2],E1[3],E1[4]); MODULE7; (* TRIG FUNCTIONS *) POUT(N7,J,K,X,X,Y,Y); MODULE8; (* PROCEDURE CALLS *) POUT(N8,J,K,X,Y,Z,Z); (* MODULE 9: ARRAY REFERENCES *) J:=1; K:=2; L:=3; E1[1]:=1.0; E1[2]:=2.0; E1[3]:=3.0; FOR I:=1 TO N9 DO P0; POUT(N9,J,K,E1[1],E1[2],E1[3],E1[4]); MODULE10; (* INTEGER ARITHMETIC *) POUT(N10,J,K,X1,X2,X3,X4); MODULE11; (* STANDARD FUNCTIONS *) POUT(N11,J,K,X,X,X,X); WRITELN('END OF WHETSTONE, ', TRUNC(IM * 10.0)/10.0:4:1, ' Million Whetstones Performed'); END. (* END WHETSTONE *)