c# - puedo - nuevas reglas de instagram 2018
¿Qué consejos de optimización puedo dar al compilador/JIT? (2)
Ya he perfilado, y ahora estoy buscando exprimir todos los posibles resultados posibles de mi punto caliente.
Sé sobre [MethodImplOptions.AggressiveInlining] y la clase ProfileOptimization . ¿Hay otros?
[Editar] Acabo de descubrir [TargetedPatchingOptOut] también. No importa, aparentemente ese no es necesario .
Ha agotado las opciones agregadas en .NET 4.5 para afectar el código jitted directamente. El siguiente paso es observar el código máquina generado para detectar cualquier ineficiencia obvia. Haga eso con el depurador, primero evite que deshabilite el optimizador. Herramientas + Opciones, Depuración, General, desmarque la opción "Suprimir optimización de JIT en la carga del módulo". Establezca un punto de interrupción en el código de acceso rápido, Depurar + Desarmar para verlo.
No hay muchos que considerar, el optimizador de jitter en general hace un excelente trabajo. Una cosa a tener en cuenta son los intentos fallidos de eliminar una comprobación de límites de matriz, la palabra clave fija es una solución insegura para eso. Un caso de esquina es un intento fallido de insertar un método y el jitter no usa registros de CPU de manera efectiva, un problema con el jitter x86 y corregido con MethodImplOptions.NoInlining. El optimizador no es demasiado eficiente para sacar el código invariante de un bucle, pero eso es algo que casi siempre se debe tener en cuenta al mirar el código C # cuando se buscan formas de optimizarlo.
Lo más importante que desea saber es cuándo ha terminado y no puede esperar hacerlo más rápido. Solo puedes llegar allí al comparar manzanas y naranjas y escribir el código de acceso rápido en código nativo usando C ++ / CLI. Asegúrate de que este código esté compilado con #pragma no administrado para que obtenga el máximo optimizador. Hay un costo asociado con el cambio del código administrado a la ejecución de código nativo, así que asegúrese de que el tiempo de ejecución del código nativo sea lo suficientemente importante. Esto de otra manera no es necesariamente fácil de hacer y ciertamente no tendrá garantía de éxito. Aunque saber que has terminado puede ahorrarte mucho tiempo tropezando en callejones sin salida.
Sí, hay más trucos :-)
De hecho, investigué un poco sobre la optimización del código C #. Hasta ahora, estos son los resultados más significativos:
- Funcs y Action''s que se pasan directamente a menudo son inlineados por el JIT''ter. Tenga en cuenta que no debe almacenarlos como variables, ya que luego se llaman como delegados. Ver también esta publicación para más detalles.
- Tenga cuidado con las sobrecargas. Llamar a Igual sin usar
IEquatable<T>
suele ser un mal plan, por lo tanto, si usa f.ex. un hash, asegúrese de implementar las sobrecargas e interfaces correctas, ya que le garantizará un montón de rendimiento. - Los genéricos llamados desde otras clases nunca son inlineados. La razón de esto es la "magia" descrita here .
- Si usas una estructura de datos, asegúrate de intentar usar una matriz en su lugar :-) Realmente, estas cosas son tan rápidas como el infierno en comparación con ... bueno, casi cualquier cosa, supongo. He optimizado un buen número de cosas al usar mis propias tablas hash y el uso de matrices en lugar de listas.
- En muchos casos, las búsquedas en la tabla son más rápidas que la computación o el uso de construcciones como búsquedas vtable, switches, múltiples instrucciones if e incluso cálculos. Este es también un buen truco si tienes ramas; la predicción de rama fallida a menudo puede convertirse en un gran dolor. Ver también esta publicación : este es un truco. Uso bastante en C # y funciona muy bien en muchos casos. Ah, y las tablas de búsqueda son matrices, por supuesto.
- Experimenta con la fabricación de clases (pequeñas). Debido a la naturaleza de los tipos de valores, algunas optimizaciones son diferentes para struct''s que para class''es. Por ejemplo, las llamadas a métodos son más simples, porque el compilador sabe exactamente a qué método se llamará. Además, las matrices de estructuras suelen ser más rápidas que las matrices de clases, ya que requieren 1 operación de memoria menos por operación de matriz.
- No use matrices multidimensionales. Aunque prefiero
Foo[]
, inclusoFoo[][]
normalmente es más rápido queFoo[,]
. - Si está copiando datos, prefiera Buffer.BlockCopy sobre Array.Copy cualquier día de la semana. También tenga cuidado con las cuerdas: las operaciones de cuerda pueden ser un drenador de rendimiento.
También solía haber una guía llamada "optimización para el procesador intel pentium" con una gran cantidad de trucos (como cambiar o multiplicar en lugar de dividir). Si bien el compilador hace un gran esfuerzo hoy en día, esto a veces también ayuda un poco.
Por supuesto, estas son solo optimizaciones; las mayores ganancias de rendimiento suelen ser el resultado de cambiar el algoritmo y / o la estructura de datos. Asegúrese de verificar qué opciones están disponibles para usted y no se restrinja demasiado por .NET Framework ... también tengo una tendencia natural a desconfiar de la implementación de .NET hasta que haya revisado el código descompilado por mi cuenta. .. hay un montón de cosas que podrían haberse implementado mucho más rápido (la mayoría de las veces por buenas razones).
HTH
Alex me señaló que Array.Copy
es realmente más rápido según algunas personas. Y como realmente no sé qué ha cambiado a lo largo de los años, decidí que el único curso de acción adecuado es crear un nuevo punto de referencia y ponerlo a prueba.
Si solo te interesan los resultados, baja. En la mayoría de los casos, la llamada a Buffer.BlockCopy
claramente supera a Array.Copy
. Probado en un Intel Skylake con 16 GB de memoria (> 10 GB gratis) en .NET 4.5.2.
Código:
static void TestNonOverlapped1(int K)
{
long total = 1000000000;
long iter = total / K;
byte[] tmp = new byte[K];
byte[] tmp2 = new byte[K];
for (long i = 0; i < iter; ++i)
{
Array.Copy(tmp, tmp2, K);
}
}
static void TestNonOverlapped2(int K)
{
long total = 1000000000;
long iter = total / K;
byte[] tmp = new byte[K];
byte[] tmp2 = new byte[K];
for (long i = 0; i < iter; ++i)
{
Buffer.BlockCopy(tmp, 0, tmp2, 0, K);
}
}
static void TestOverlapped1(int K)
{
long total = 1000000000;
long iter = total / K;
byte[] tmp = new byte[K + 16];
for (long i = 0; i < iter; ++i)
{
Array.Copy(tmp, 0, tmp, 16, K);
}
}
static void TestOverlapped2(int K)
{
long total = 1000000000;
long iter = total / K;
byte[] tmp = new byte[K + 16];
for (long i = 0; i < iter; ++i)
{
Buffer.BlockCopy(tmp, 0, tmp, 16, K);
}
}
static void Main(string[] args)
{
for (int i = 0; i < 10; ++i)
{
int N = 16 << i;
Console.WriteLine("Block size: {0} bytes", N);
Stopwatch sw = Stopwatch.StartNew();
{
sw.Restart();
TestNonOverlapped1(N);
Console.WriteLine("Non-overlapped Array.Copy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
}
{
sw.Restart();
TestNonOverlapped2(N);
Console.WriteLine("Non-overlapped Buffer.BlockCopy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
}
{
sw.Restart();
TestOverlapped1(N);
Console.WriteLine("Overlapped Array.Copy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
}
{
sw.Restart();
TestOverlapped2(N);
Console.WriteLine("Overlapped Buffer.BlockCopy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
}
Console.WriteLine("-------------------------");
}
Console.ReadLine();
}
Resultados en x86 JIT:
Block size: 16 bytes
Non-overlapped Array.Copy: 4267.52 ms
Non-overlapped Buffer.BlockCopy: 2887.05 ms
Overlapped Array.Copy: 3305.01 ms
Overlapped Buffer.BlockCopy: 2670.18 ms
-------------------------
Block size: 32 bytes
Non-overlapped Array.Copy: 1327.55 ms
Non-overlapped Buffer.BlockCopy: 763.89 ms
Overlapped Array.Copy: 2334.91 ms
Overlapped Buffer.BlockCopy: 2158.49 ms
-------------------------
Block size: 64 bytes
Non-overlapped Array.Copy: 705.76 ms
Non-overlapped Buffer.BlockCopy: 390.63 ms
Overlapped Array.Copy: 1303.00 ms
Overlapped Buffer.BlockCopy: 1103.89 ms
-------------------------
Block size: 128 bytes
Non-overlapped Array.Copy: 361.18 ms
Non-overlapped Buffer.BlockCopy: 219.77 ms
Overlapped Array.Copy: 620.21 ms
Overlapped Buffer.BlockCopy: 577.20 ms
-------------------------
Block size: 256 bytes
Non-overlapped Array.Copy: 192.92 ms
Non-overlapped Buffer.BlockCopy: 108.71 ms
Overlapped Array.Copy: 347.63 ms
Overlapped Buffer.BlockCopy: 353.40 ms
-------------------------
Block size: 512 bytes
Non-overlapped Array.Copy: 104.69 ms
Non-overlapped Buffer.BlockCopy: 65.65 ms
Overlapped Array.Copy: 211.77 ms
Overlapped Buffer.BlockCopy: 202.94 ms
-------------------------
Block size: 1024 bytes
Non-overlapped Array.Copy: 52.93 ms
Non-overlapped Buffer.BlockCopy: 38.84 ms
Overlapped Array.Copy: 144.39 ms
Overlapped Buffer.BlockCopy: 154.09 ms
-------------------------
Block size: 2048 bytes
Non-overlapped Array.Copy: 45.64 ms
Non-overlapped Buffer.BlockCopy: 30.11 ms
Overlapped Array.Copy: 118.33 ms
Overlapped Buffer.BlockCopy: 109.16 ms
-------------------------
Block size: 4096 bytes
Non-overlapped Array.Copy: 30.93 ms
Non-overlapped Buffer.BlockCopy: 30.72 ms
Overlapped Array.Copy: 119.73 ms
Overlapped Buffer.BlockCopy: 104.66 ms
-------------------------
Block size: 8192 bytes
Non-overlapped Array.Copy: 30.37 ms
Non-overlapped Buffer.BlockCopy: 26.63 ms
Overlapped Array.Copy: 90.46 ms
Overlapped Buffer.BlockCopy: 87.40 ms
-------------------------
Resultados en x64 JIT:
Block size: 16 bytes
Non-overlapped Array.Copy: 1252.71 ms
Non-overlapped Buffer.BlockCopy: 694.34 ms
Overlapped Array.Copy: 701.27 ms
Overlapped Buffer.BlockCopy: 573.34 ms
-------------------------
Block size: 32 bytes
Non-overlapped Array.Copy: 995.47 ms
Non-overlapped Buffer.BlockCopy: 654.70 ms
Overlapped Array.Copy: 398.48 ms
Overlapped Buffer.BlockCopy: 336.86 ms
-------------------------
Block size: 64 bytes
Non-overlapped Array.Copy: 498.86 ms
Non-overlapped Buffer.BlockCopy: 329.15 ms
Overlapped Array.Copy: 218.43 ms
Overlapped Buffer.BlockCopy: 179.95 ms
-------------------------
Block size: 128 bytes
Non-overlapped Array.Copy: 263.00 ms
Non-overlapped Buffer.BlockCopy: 196.71 ms
Overlapped Array.Copy: 137.21 ms
Overlapped Buffer.BlockCopy: 107.02 ms
-------------------------
Block size: 256 bytes
Non-overlapped Array.Copy: 144.31 ms
Non-overlapped Buffer.BlockCopy: 101.23 ms
Overlapped Array.Copy: 85.49 ms
Overlapped Buffer.BlockCopy: 69.30 ms
-------------------------
Block size: 512 bytes
Non-overlapped Array.Copy: 76.76 ms
Non-overlapped Buffer.BlockCopy: 55.31 ms
Overlapped Array.Copy: 61.99 ms
Overlapped Buffer.BlockCopy: 54.06 ms
-------------------------
Block size: 1024 bytes
Non-overlapped Array.Copy: 44.01 ms
Non-overlapped Buffer.BlockCopy: 33.30 ms
Overlapped Array.Copy: 53.13 ms
Overlapped Buffer.BlockCopy: 51.36 ms
-------------------------
Block size: 2048 bytes
Non-overlapped Array.Copy: 27.05 ms
Non-overlapped Buffer.BlockCopy: 25.57 ms
Overlapped Array.Copy: 46.86 ms
Overlapped Buffer.BlockCopy: 47.83 ms
-------------------------
Block size: 4096 bytes
Non-overlapped Array.Copy: 29.11 ms
Non-overlapped Buffer.BlockCopy: 25.12 ms
Overlapped Array.Copy: 45.05 ms
Overlapped Buffer.BlockCopy: 47.84 ms
-------------------------
Block size: 8192 bytes
Non-overlapped Array.Copy: 24.95 ms
Non-overlapped Buffer.BlockCopy: 21.52 ms
Overlapped Array.Copy: 43.81 ms
Overlapped Buffer.BlockCopy: 43.22 ms
-------------------------