Sorpresa de rendimiento con tipos" as " y nullable


Solo estoy revisando el capítulo 4 de C# en Profundidad que trata sobre los tipos nullables, y estoy agregando una sección sobre el uso del operador "as", que le permite escribir:

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    ... // Use x.Value in here
}

Pensé que esto era realmente limpio, y que podría mejorar el rendimiento sobre el equivalente de C# 1, usando "is" seguido de un cast - después de todo, de esta manera solo necesitamos pedir la comprobación de tipo dinámico una vez, y luego una simple comprobación de valor.

Este no parece ser el caso, sin embargo. He incluido una prueba de muestra aplicación a continuación, que básicamente suma todos los enteros dentro de una matriz de objetos - pero la matriz contiene una gran cantidad de referencias nulas y referencias de cadena, así como enteros en caja. El benchmark mide el código que tendrías que usar en C# 1, el código usando el operador "as", y solo para patadas una solución LINQ. Para mi sorpresa, el código de C# 1 es 20 veces más rápido en este caso, e incluso el código LINQ (que habría esperado que fuera más lento, dados los iteradores involucrados) supera al código "as".

Es el ¿La implementación de. NET de isinst para tipos nullables es realmente lenta? Es el adicional unbox.any que causa el problema? Hay otra explicación para esto? Por el momento, siento que voy a tener que incluir una advertencia contra el uso de esto en situaciones sensibles al rendimiento...

Resultados:

Reparto: 10000000: 121
As: 10000000: 2211
LINQ: 10000000: 2143

Código:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i+1] = "";
            values[i+2] = 1;
        }

        FindSumWithCast(values);
        FindSumWithAs(values);
        FindSumWithLinq(values);
    }

    static void FindSumWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int) o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }

    static void FindSumWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }

    static void FindSumWithLinq(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = values.OfType<int>().Sum();
        sw.Stop();
        Console.WriteLine("LINQ: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }
}
Author: Jon Seigel, 2009-10-17

10 answers

Claramente el código máquina que el compilador JIT puede generar para el primer caso es mucho más eficiente. Una regla que realmente ayuda allí es que un objeto solo puede ser unboxed a una variable que tiene el mismo tipo que el valor en caja. Eso permite que el compilador JIT genere código muy eficiente, no hay que considerar conversiones de valor.

El es La prueba del operador es fácil, simplemente verifique si el objeto no es nulo y es del tipo esperado, solo toma unos pocos códigos de máquina instrucción. El cast también es fácil, el compilador JIT conoce la ubicación de los bits de valor en el objeto y los usa directamente. No se produce ninguna copia o conversión, todo el código de la máquina está en línea y solo toma alrededor de una docena de instrucciones. Esto necesitaba ser realmente eficiente en. NET 1.0 cuando el boxeo era común.

Casting to int? toma mucho más trabajo. La representación del valor del entero en caja no es compatible con el diseño de memoria de Nullable<int>. Se requiere una conversión y el código es complicado debido a los posibles tipos de enumeración en caja. El compilador JIT genera una llamada a una función auxiliar de CLR llamada JIT_Unbox_Nullable para realizar el trabajo. Esta es una función de propósito general para cualquier tipo de valor, hay mucho código para verificar los tipos. Y el valor se copia. Es difícil estimar el costo ya que este código está encerrado dentro de mscorwks.dll, pero cientos de instrucciones de código máquina es probable.

El método de extensión Linq OfType() también usa el operador is y el cast. Este es sin embargo un molde a un tipo genérico. El compilador JIT genera una llamada a una función auxiliar, JIT_Unbox () que puede realizar un cast a un tipo de valor arbitrario. No tengo una gran explicación de por qué es tan lento como el molde para Nullable<int>, dado que menos trabajo debería ser necesario. Sospecho que ngen.exe podría causar problemas aquí.

 195
Author: Hans Passant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-20 12:06:33

Me parece que el isinst es realmente lento en tipos nullables. En el método FindSumWithCast cambié

if (o is int)

A

if (o is int?)

Que también ralentiza significativamente la ejecución. La única diferencia que puedo ver es que

isinst     [mscorlib]System.Int32

Se cambia a

isinst     valuetype [mscorlib]System.Nullable`1<int32>
 23
Author: Dirk Vollmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-17 20:10:02

Esto comenzó originalmente como un Comentario a la excelente respuesta de Hans Passant, pero se hizo demasiado largo, así que quiero agregar algunos bits aquí:

Primero, el operador C# as emitirá una instrucción isinst IL (también lo hace el operador is). (Otra instrucción interesante es castclass, emitida cuando se hace un cast directo y el compilador sabe que la comprobación en tiempo de ejecución no se puede omitir.)

Esto es lo que hace isinst ( ECMA 335 Partición III, 4.6):

Formato: isinst typeTok

typeTok es un token de metadatos (un typeref, typedef o typespec), indicando la clase deseada.

Si typeTok es un tipo de valor no anulable o un tipo de parámetro genérico, se interpreta como "boxed" typeTok.

Si typeTok es un tipo nullable, Nullable<T>, se interpreta como" en caja " T

Lo más importante:

Si el tipo real (no el verificador tracked type) of obj is verifier-assignable-to the type typeTok then isinstsuccesses and obj (as result ) is returned unchanged while verification tracks its type as typeTok . A diferencia de las coerciones (§1.6) y las conversiones (§3.27), isinst nunca cambia el tipo real de un objeto y preserva la identidad del objeto (ver Partición I).

Entonces, el asesino del rendimiento no es isinst en este caso, sino el adicional unbox.any. Este no estaba claro por la respuesta de Hans, ya que solo miró el código nervioso. En general, el compilador de C# emitirá un unbox.any después de un isinst T? (pero lo omitirá en caso de que lo haga isinst T, cuando T es un tipo de referencia).

¿por Qué hace eso? isinst T? nunca tiene el efecto que hubiera sido obvio, es decir, se obtiene un T?. En su lugar, todas estas instrucciones aseguran es que usted tiene un "boxed T" que puede ser unboxed a T?. Para obtener un T? real, todavía tenemos que descomprimir nuestro "boxed T" a T?, que es por eso que el compilador emite un unbox.any después de isinst. Si lo piensas, esto tiene sentido porque el" formato de caja " para T? es solo un "boxed T" y hacer que castclass y isinst realicen el unbox sería inconsistente.

Respaldando el hallazgo de Hans con alguna información del estándar , aquí va:

(ECMA 335 Partición III, 4.33): unbox.any

Cuando se aplica a la forma en caja de un tipo de valor, la instrucción unbox.any extrae el valor contenido en obj (de tipo O). (Es equivalente a unbox seguido de ldobj.) Cuando se aplica a un tipo de referencia, la instrucción unbox.any tiene el mismo efecto que castclass typeTok.

(ECMA 335 Partición III, 4.32): unbox

Normalmente, unbox simplemente calcula la dirección del tipo de valor que ya está presente dentro del objeto en caja. Este enfoque no es posible al descomprimir tipos de valores nullables. Porque Nullable<T> los valores se convierten en cajas Ts durante la caja operación, una implementación a menudo debe fabricar un nuevo Nullable<T> en el montón y calcular la dirección del objeto recién asignado.

 22
Author: Johannes Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-18 17:48:19

Curiosamente, pasé comentarios sobre el soporte del operador a través de dynamic siendo un orden de magnitud más lento para Nullable<T> (similar a esta prueba temprana)-sospecho por razones muy similares.

Tengo que amar Nullable<T>. Otro divertido es que a pesar de que el JIT detecta (y elimina) null para estructuras no nullables, lo borks para Nullable<T>:

using System;
using System.Diagnostics;
static class Program {
    static void Main() { 
        // JIT
        TestUnrestricted<int>(1,5);
        TestUnrestricted<string>("abc",5);
        TestUnrestricted<int?>(1,5);
        TestNullable<int>(1, 5);

        const int LOOP = 100000000;
        Console.WriteLine(TestUnrestricted<int>(1, LOOP));
        Console.WriteLine(TestUnrestricted<string>("abc", LOOP));
        Console.WriteLine(TestUnrestricted<int?>(1, LOOP));
        Console.WriteLine(TestNullable<int>(1, LOOP));

    }
    static long TestUnrestricted<T>(T x, int loop) {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
    static long TestNullable<T>(T? x, int loop) where T : struct {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
}
 19
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-17 21:26:50

Este es el resultado de FindSumWithAsAndHas arriba: alt text http://www.freeimagehosting.net/uploads/9e3c0bfb75.png

Este es el resultado de FindSumWithCast: alt text http://www.freeimagehosting.net/uploads/ce8a5a3934.png

Conclusiones:

  • Usando as, primero prueba si un objeto es una instancia de Int32; debajo del capó está usando isinst Int32 (que es similar al código escrito a mano: if (o is int) ). Y usando as, también desempaquetar incondicionalmente el objeto. Y es un verdadero asesino del rendimiento para llamar a una propiedad (sigue siendo una función bajo el capó), IL_0027

  • Usando cast, primero prueba si el objeto es un int if (o is int); debajo del capó esto está usando isinst Int32. Si es una instancia de int, entonces puede descomprimir de forma segura el valor, IL_002D

En pocas palabras, este es el pseudo-código de usar as enfoque:

int? x;

(x.HasValue, x.Value) = (o isinst Int32, o unbox Int32)

if (x.HasValue)
    sum += x.Value;    

Y este es el pseudo-código de usar cast enfoque:

if (o isinst Int32)
    sum += (o unbox Int32)

Así que el cast ((int)a[i], bueno, la sintaxis parece un cast, pero en realidad es unboxing, cast y unboxing comparten la misma sintaxis, la próxima vez seré pedante con la terminología correcta) enfoque es realmente más rápido, solo se necesita para unboxing un valor cuando un objeto es decididamente un int. No se puede decir lo mismo de usar un enfoque as.

 12
Author: Michael Buen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-24 01:14:30

Perfilar más:

using System;
using System.Diagnostics;

class Program
{
    const int Size = 30000000;

    static void Main(string[] args)
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "";
            values[i + 2] = 1;
        }

        FindSumWithIsThenCast(values);

        FindSumWithAsThenHasThenValue(values);
        FindSumWithAsThenHasThenCast(values);

        FindSumWithManualAs(values);
        FindSumWithAsThenManualHasThenValue(values);



        Console.ReadLine();
    }

    static void FindSumWithIsThenCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int)o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenValue(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (x.HasValue)
            {
                sum += (int)o;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Has then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithManualAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            bool hasValue = o is int;
            int x = hasValue ? (int)o : 0;

            if (hasValue)
            {
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Manual As: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenManualHasThenValue(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Manual Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

}

Salida:

Is then Cast: 10000000 : 303
As then Has then Value: 10000000 : 3524
As then Has then Cast: 10000000 : 3272
Manual As: 10000000 : 395
As then Manual Has then Value: 10000000 : 3282

¿Qué podemos inferir de estas cifras?

  • En primer lugar, el enfoque es-entonces-fundido es significativamente más rápido que como enfoque. 303 vs 3524
  • Segundo, .El valor es ligeramente más lento que el casting. 3524 vs 3272
  • Tercero, .HasValue es ligeramente más lento que usar manual has (es decir, usar es). 3524 vs 3282
  • Cuarto, haciendo una comparación de apple a apple (es decir, tanto la asignación de simulado HasValue y la conversión simulada Valor que sucede juntos) entre simulado como y real como enfoque, podemos ver simulado como es significativamente más rápido que real como. 395 vs 3524
  • Por último, basado en la primera y cuarta conclusión, hay algo mal con como aplicación ^_^
 9
Author: Michael Buen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-21 14:17:32

No tengo tiempo para probarlo, pero es posible que desee tener:

foreach (object o in values)
        {
            int? x = o as int?;

Como

int? x;
foreach (object o in values)
        {
            x = o as int?;

Está creando un nuevo objeto cada vez, que no explicará completamente el problema, pero puede contribuir.

 8
Author: James Black,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-17 19:59:51

Probé la construcción exacta de verificación de tipo

typeof(int) == item.GetType(), que funciona tan rápido como la versión item is int, y siempre devuelve el número (énfasis: incluso si escribió un Nullable<int> a la matriz, tendría que usar typeof(int)). También necesita una verificación adicional null != item aquí.

Sin embargo

typeof(int?) == item.GetType() se mantiene rápido (en contraste con item is int?), pero siempre devuelve false.

El typeof-construct es en mis ojos la forma más rápida para exact type checking, ya que utiliza el RuntimeTypeHandle. Dado que los tipos exactos en este caso no coinciden con nullable, mi conjetura es que is/as tiene que hacer un heavylifting adicional aquí para asegurarse de que sea de hecho una instancia de un tipo Nullable.

Y honestamente: ¿qué te compra tu is Nullable<xxx> plus HasValue? Nada. Siempre puede ir directamente al tipo subyacente (valor) (en este caso). Obtienes el valor o "no, no una instancia del tipo que estabas pidiendo". Incluso si escribiste (int?)null a la matriz, la comprobación de tipo devolverá falso.

 8
Author: dalo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-19 10:01:21
using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "";
            values[i + 2] = 1;
        }

        FindSumWithCast(values);
        FindSumWithAsAndHas(values);
        FindSumWithAsAndIs(values);


        FindSumWithIsThenAs(values);
        FindSumWithIsThenConvert(values);

        FindSumWithLinq(values);



        Console.ReadLine();
    }

    static void FindSumWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int)o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsAndHas(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As and Has: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }


    static void FindSumWithAsAndIs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As and Is: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }







    static void FindSumWithIsThenAs(object[] values)
    {
        // Apple-to-apple comparison with Cast routine above.
        // Using the similar steps in Cast routine above,
        // the AS here cannot be slower than Linq.



        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {

            if (o is int)
            {
                int? x = o as int?;
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then As: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithIsThenConvert(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {            
            if (o is int)
            {
                int x = Convert.ToInt32(o);
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Convert: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }



    static void FindSumWithLinq(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = values.OfType<int>().Sum();
        sw.Stop();
        Console.WriteLine("LINQ: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }
}

Salidas:

Cast: 10000000 : 456
As and Has: 10000000 : 2103
As and Is: 10000000 : 2029
Is then As: 10000000 : 1376
Is then Convert: 10000000 : 566
LINQ: 10000000 : 1811

[EDITAR: 2010-06-19]

Nota: La prueba anterior se realizó dentro de VS, configuration debug, usando VS2009, usando Core i7(company development machine).

Lo siguiente se hizo en mi máquina usando Core 2 Duo, usando VS2010

Inside VS, Configuration: Debug

Cast: 10000000 : 309
As and Has: 10000000 : 3322
As and Is: 10000000 : 3249
Is then As: 10000000 : 1926
Is then Convert: 10000000 : 410
LINQ: 10000000 : 2018




Outside VS, Configuration: Debug

Cast: 10000000 : 303
As and Has: 10000000 : 3314
As and Is: 10000000 : 3230
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 418
LINQ: 10000000 : 1944




Inside VS, Configuration: Release

Cast: 10000000 : 305
As and Has: 10000000 : 3327
As and Is: 10000000 : 3265
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1932




Outside VS, Configuration: Release

Cast: 10000000 : 301
As and Has: 10000000 : 3274
As and Is: 10000000 : 3240
Is then As: 10000000 : 1904
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1936
 7
Author: Michael Buen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-19 10:16:59

Para mantener esta respuesta actualizada, vale la pena mencionar que la mayor parte de la discusión en esta página ahora es discutible con C# 7.1 y . NET 4.7 que soporta una sintaxis delgada que también produce el mejor código IL.

El ejemplo original del OP...

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    // ...use x.Value in here
}

Se convierte simplemente...

if (o is int x)
{
    // ...use x in here
}

He encontrado que un uso común para la nueva sintaxis es cuando se está escribiendo un. NET tipo de valor (es decir, struct en C#) que implementa IEquatable<MyStruct> (como la mayoría debería). Después de implementar el método strongly-typed Equals(MyStruct other), ahora puede redirigir con gracia la anulación sin tipo Equals(Object obj) (heredada de Object) de la siguiente manera:

public override bool Equals(Object obj) => obj is MyStruct o && Equals(o);

 


Apéndice: El código Release build IL para las dos primeras funciones de ejemplo mostradas arriba en esta respuesta (respectivamente) se dan aquí. Mientras que el código IL para la nueva sintaxis es de hecho 1 byte más pequeño, en su mayoría gana a lo grande haciendo cero llamadas (vs. dos) y evitar por completo la operación unbox cuando sea posible.

// static void test1(Object o, ref int y)
// {
//     int? x = o as int?;
//     if (x.HasValue)
//         y = x.Value;
// }

[0] valuetype [mscorlib]Nullable`1<int32> x
        ldarg.0
        isinst [mscorlib]Nullable`1<int32>
        unbox.any [mscorlib]Nullable`1<int32>
        stloc.0
        ldloca.s x
        call instance bool [mscorlib]Nullable`1<int32>::get_HasValue()
        brfalse.s L_001e
        ldarg.1
        ldloca.s x
        call instance !0 [mscorlib]Nullable`1<int32>::get_Value()
        stind.i4
L_001e: ret

// static void test2(Object o, ref int y)
// {
//     if (o is int x)
//         y = x;
// }

[0] int32 x,
[1] object obj2
        ldarg.0
        stloc.1
        ldloc.1
        isinst int32
        ldnull
        cgt.un
        dup
        brtrue.s L_0011
        ldc.i4.0
        br.s L_0017
L_0011: ldloc.1
        unbox.any int32
L_0017: stloc.0
        brfalse.s L_001d
        ldarg.1
        ldloc.0
        stind.i4
L_001d: ret

Para pruebas adicionales que corroboren mi observación sobre el rendimiento de la nueva sintaxis C#7 que supera las opciones disponibles anteriormente, vea aquí (en particular, el ejemplo 'D').

 7
Author: Glenn Slayden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-28 00:00:59