web-dev-qa-db-fra.com

La réflexion est lente

J'ai récemment créé une couche d'interface pour distinguer le DataAccessProvider de notre couche de logique métier. Avec cette approche, nous pouvons changer notre choix de DataAccessProvider quand nous le voulons en modifiant les valeurs dans Web/App.Config. (plus de détails peuvent être fournis si nécessaire).

Quoi qu'il en soit, pour ce faire, nous utilisons la réflexion pour accomplir notre classe DataProvider sur laquelle nous pouvons travailler.

/// <summary>
/// The constructor will create a new provider with the use of reflection.
/// If the Assembly could not be loaded an AssemblyNotFoundException will be thrown.
/// </summary>
public DataAccessProviderFactory()
{
    string providerName = ConfigurationManager.AppSettings["DataProvider"];
    string providerFactoryName = ConfigurationManager.AppSettings["DataProviderFactory"];
    try
    {
        activeProvider = Assembly.Load(providerName);
        activeDataProviderFactory = (IDataProviderFactory)activeProvider.CreateInstance(providerFactoryName);
    }
    catch
    {
        throw new AssemblyNotFoundException();
    }
}

Mais maintenant je me demande à quel point la réflexion est lente?

63
user29964

Dans la plupart des cas: plus que suffisamment rapide. Par exemple, si vous l'utilisez pour créer un objet wrapper DAL, le temps nécessaire pour créer l'objet par réflexion sera minuscule par rapport au temps qu'il doit se connecter à un réseau. L'optimisation serait donc une perte de temps.

Si vous utilisez la réflexion dans une boucle serrée, il existe des astuces pour l'améliorer:

  • génériques (en utilisant un wrapper where T : new() et MakeGenericType)
  • Delegate.CreateDelegate (à un délégué tapé; ne fonctionne pas pour les constructeurs)
  • Reflection.Emit - hardcore
  • Expression (comme Delegate.CreateDelegate, mais plus flexible, et fonctionne pour les constructeurs)

Mais pour vos besoins, CreateInstance est parfaitement bien. Restez avec cela et gardez les choses simples.


Edit: alors que le point sur les performances relatives demeure, et bien que la chose la plus importante, "la mesurer", reste, je devrais clarifier certains des points ci-dessus. Parfois ... cela importe . Mesurez d'abord. Cependant, si vous le trouvez est trop lent, vous voudrez peut-être regarder quelque chose comme FastMember , qui fait tout le Reflection.Emit codez tranquillement en arrière-plan, pour vous donner une belle API facile; par exemple:

var accessor = TypeAccessor.Create(type);
List<object> results = new List<object>();
foreach(var row in rows) {
    object obj = accessor.CreateNew();
    foreach(var col in cols) {
        accessor[obj, col.Name] = col.Value;
    }
    results.Add(obj);
}

ce qui est simple, mais sera très rapide. Dans l'exemple spécifique que je mentionne à propos d'un wrapper DAL - si vous faites ces lots, considérez quelque chose comme dapper , qui à nouveau fait tout le Reflection.Emit code en arrière-plan pour vous offrir l'API la plus rapide possible mais facile à utiliser:

int id = 12345;
var orders = connection.Query<Order>(
    "select top 10 * from Orders where CustomerId = @id order by Id desc",
    new { id }).ToList();
75
Marc Gravell

C'est plus lent que le code non réfléchissant. L'important n'est pas si c'est lent, mais si c'est lent là où ça compte . Par exemple, si vous instanciez des objets en utilisant la réflexion dans un environnement Web où la concordance attendue peut augmenter jusqu'à 10K, elle sera lente.

Quoi qu'il en soit, il est bon de ne pas se soucier des performances à l'avance. Si les choses s'avèrent lentes, vous pouvez toujours les accélérer si vous avez conçu les choses correctement afin que les pièces que vous attendiez à avoir besoin d'optimisation à l'avenir soient localisées.

Vous pouvez consulter ce fameux article si vous avez besoin d'accélérer:

Dynamique ... mais rapide: l'histoire de trois singes, un loup et les classes DynamicMethod et ILGenerator

17
majkinetor
9
Ruben Steins

La réflexion n'est pas si lente. Invoquer une méthode par réflexion est environ 3 fois plus lent que la manière normale. Ce n'est pas un problème si vous ne faites cela qu'une seule fois ou dans des situations non critiques. Si vous l'utilisez 10 000 fois dans une méthode critique, je considérerais de changer l'implémentation.

5
Enyra

J'ai pensé que je ferais un test rapide pour démontrer comment la réflexion lente est comparée à sans.

avec réflexion

  • Instanciation de 58 objets en itérant sur chacun de leurs attributs et en les faisant correspondre
  • Temps total: 52254 nanosecondes

    while (reader.Read()) {
        string[] columns = reader.CurrentRecord;
        CdsRawPayfileEntry toAdd = new CdsRawPayfileEntry();
        IEnumerable<PropertyInfo> rawPayFileAttributes = typeof(CdsRawPayfileEntry).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(CustomIndexAttribute)));
        foreach (var property in rawPayFileAttributes) {
            int propertyIndex = ((CustomIndexAttribute)property.GetCustomAttribute(typeof(CustomIndexAttribute))).Index;
            if (propertyIndex < columns.Length)
                property.SetValue(toReturn, columns[propertyIndex]);
            else
                break;
        }
    }
    

sans réflexion

  • Instanciation de 58 objets en créant un nouvel objet
  • Temps total: 868 nanosecondes

        while (reader2.Read()) {
            string[] columns = reader2.CurrentRecord;
            CdsRawPayfileEntry toAdd = new CdsRawPayfileEntry() {
                ColumnZero = columns[0],
                ColumnOne = columns[1],
                ColumnTwo = columns[2],
                ColumnThree = columns[3],
                ColumnFour = columns[4],
                ColumnFive = columns[5],
                ColumnSix = columns[6],
                ColumnSeven = columns[7],
                ColumnEight = columns[8],
                ColumnNine = columns[9],
                ColumnTen = columns[10],
                ColumnEleven = columns[11],
                ColumnTwelve = columns[12],
                ColumnThirteen = columns[13],
                ColumnFourteen = columns[14],
                ColumnFifteen = columns[15],
                ColumnSixteen = columns[16],
                ColumnSeventeen = columns[17]
            };
        }
    

Bien que pas tout à fait juste car la réflexion doit également récupérer un attribut spécifique de chaque propriété 58 * 18 fois en plus de créer un nouvel objet via la réflexion, mais elle fournit au moins une certaine perspective.

5
Levi Fuller

En plus de suivre les liens fournis dans d'autres réponses et de vous assurer que vous n'écrivez pas de code "pathalogiquement mauvais", la meilleure réponse à cela est pour moi de le tester vous-même.

Vous seul savez où se trouvent les goulots d'étranglement, combien de fois votre code de réflexion sera utilisé, si le code de réflexion sera en boucles serrées, etc. Vous connaissez votre analyse de rentabilisation, combien d'utilisateurs accéderont à votre site, quelles sont les exigences de performance.

Cependant, étant donné l'extrait de code que vous avez montré ici, je suppose que les frais généraux de réflexion ne seront pas un problème énorme.

Les fonctionnalités de test Web et de test de performances de VS.NET devraient rendre la mesure des performances de ce code assez simple.

Si vous n'utilisez pas la réflexion, à quoi ressemblera votre code? Quelles limites cela aura-t-il? Il se peut que vous ne puissiez pas vivre avec les limitations avec lesquelles vous vous trouvez si vous supprimez le code de réflexion. Il pourrait être utile d'essayer de concevoir ce code sans réflexion pour voir s'il est possible ou si l'alternative est souhaitable.

3
Martin Peck

Je faisais quelque chose de similaire jusqu'à ce que je commence à jouer avec IoC. J'utiliserais une définition d'objet Spring pour spécifier le fournisseur de données - SQL, XML ou Mocks!

0
n8wrl