2008-11-06 9 views
30

Esto es solo para satisfacer mi propia curiosidad.¿Es posible escribir la función InvSqrt() rápida de Quake en C#?

¿Existe una implementación de este:

float InvSqrt (float x) 
{ 
    float xhalf = 0.5f*x; 
    int i = *(int*)&x; 
    i = 0x5f3759df - (i>>1); 
    x = *(float*)&i; 
    x = x*(1.5f - xhalf*x*x); 
    return x; 
} 

en C#? Si existe, publique el código.

Supongo que debería haber mencionado que estaba buscando una implementación "segura" ... En cualquier caso, el código BitConverter resuelve el problema. La idea de unión es interesante. Lo probaré y publicaré mis resultados.

Edit: Como era de esperar, el método inseguro es el más rápido, seguido por el uso de una unión (dentro de la función), seguido por el BitConverter. Las funciones se ejecutaron 10000000 veces, y utilicé la clase System.Diagnostics.Stopwatch para medir el tiempo. Los resultados de los cálculos se muestran entre paréntesis.

Input: 79.67 
BitConverter Method: 00:00:01.2809018 (0.1120187) 
Union Method: 00:00:00.6838758 (0.1120187) 
Unsafe Method: 00:00:00.3376401 (0.1120187) 

Para completar, he probado el método integrado Math.pow, y el método "naive" (1/sqrt (x)).

Math.Pow(x, -0.5): 00:00:01.7133228 (0.112034710535584) 
1/Math.Sqrt(x): 00:00:00.3757084 (0.1120347) 

La diferencia entre 1/Math.Sqrt() es tan pequeño que no creo que uno tiene que recurrir al método inseguro InvSqrt rápido() en C# (o cualquier otro método inseguro). A menos que uno realmente necesite exprimir el último bit de jugo de la CPU ... 1/Math.Sqrt() también es mucho más preciso.

+0

Para completar, debe ejecutar una prueba usando "1/math.Sqrt()" también. –

+0

Lo hice, y otro escenario. Actualizaré los puntos de referencia tan pronto como pueda verificar los resultados. – ilitirit

+0

Creo que deberías haber hecho tus pruebas con un set más grande, para que tuvieran unos segundos en completarse. – Gleno

Respuesta

13

Usted debe ser capaz de utilizar el StructLayout y FieldOffset atribuye la falsificación de una unión a datos simples antiguos como flotantes y enteros.

[StructLayout(LayoutKind.Explicit, Size=4)] 
private struct IntFloat { 
    [FieldOffset(0)] 
    public float floatValue; 

    [FieldOffset(0)] 
    public int intValue; 

    // redundant assignment to avoid any complaints about uninitialized members 
    IntFloat(int x) { 
     floatValue = 0; 
     intValue = x; 
    } 

    IntFloat(float x) { 
     intValue = 0; 
     floatValue = x; 
    } 

    public static explicit operator float (IntFloat x) { 
     return x.floatValue; 
    } 

    public static explicit operator int (IntFloat x) { 
     return x.intValue; 
    } 

    public static explicit operator IntFloat (int i) { 
     return new IntFloat(i); 
    } 
    public static explicit operator IntFloat (float f) { 
     return new IntFloat(f); 
    } 
} 

Luego, traducir InvSqrt es fácil.

+0

Solo una palabra de advertencia de que acabo de implementar (solo por diversión) FastInvSqrt usando este truco y fue significativamente más lento que usando la implementación más directa con punteros . Por supuesto, en estos días, incluso con los punteros FastInvSqrt es más lento que 1/Math.Sqrt (x) ... – Martin

+0

No muy sorprendido. ¡Gracias por la respuesta! =) –

+0

Como nota al margen: https://github.com/ekmett/approximate/blob/master/cbits/fast.c proporciona un registro aproximado, exp, pow, etc. Aquellos que al menos parecen ser una victoria en términos de ciclos de reloj, pero probablemente no en C#. =) –

1

No veo por qué no sería posible usar la opción de compilador inseguro .

+0

http://msdn.microsoft.com/en-us/library/chfa2zb8(VS.80).aspx – VVS

+0

Si alguien programa en unidad, tiene esta razón: Algunos dispositivos y reproductores web no admiten códigos inseguros debido a la seguridad razones. –

+0

Es cierto. Esta respuesta fue de 2008 ... – Inisheer

7

Utilice BitConverter si desea evitar el código inseguro.

float InvSqrt(float x) 
{ 
    float xhalf = 0.5f * x; 
    int i = BitConverter.ToInt32(BitConverter.GetBytes(x), 0); 
    i = 0x5f3759df - (i >> 1); 
    x = BitConverter.ToSingle(BitConverter.GetBytes(i), 0); 
    x = x * (1.5f - xhalf * x * x); 
    return x; 
} 

De lo contrario, el código C# es exactamente el mismo que el código C que diste, excepto que el método tiene que ser marcado como inseguro:

unsafe float InvSqrt(float x) { ... } 
+2

Pero eso deja abierta la pregunta: "¿Sigue siendo rápido usando BitConverter?" –

+0

El método BitConverter causa dos asignaciones de matriz, lo que podría ser un problema de rendimiento. (Desafortunadamente, no hay BitConverter.Método SingleToInt32Bits, análogo a BitConverter.DoubleToInt64Bits, que debe ser rápido y no lineal.) –

6

Definitivamente es posible en modo inseguro. Tenga en cuenta que, aunque en el código fuente de Quake 3 se usó la constante 0x5f3759df, numerical research showed, la constante 0x5f375a86 en realidad produce mejores resultados para las aproximaciones de Newton.

Cuestiones relacionadas