Je me souviens quand .Net est sorti à l’origine il y a environ 20 ans et que Microsoft avait créé un site Web appelé « animalerie » ou quelque chose, où ils ont pu « prouver » que .Net et SQL Server étaient plus rapides que l’équivalent Java et Oracle. Bien sûr, la version Java a été construite en utilisant les meilleures pratiques de Java à l’époque, tandis que la version .Net implémentait presque entièrement 100% de sa logique métier dans des procédures stockées. Inutile de le dire bien sûr, mais c’était une comparaison ridicule, car aucun être humain (sain) ne mettrait autant de logique métier dans des procédures stockées. Lorsque nous effectuons des benchmarks, il est important que nous mesurions les meilleures pratiques, et utilisation typique. L’analyse comparative des anti-modèles et des procédures stockées ne nous dit rien sur le modèle de développement que nous voyons généralement lorsque nous commençons réellement à utiliser la chose.
Il est également important que nous puissions isoler autant que possible la bibliothèque que nous mesurons, pour éviter de mesurer des choses non pertinentes. Dans cet article, je vais vous fournir un exemple d’Entity Framework, ressemblant généralement au type de code que vous verriez être utilisé, par rapport à l’équivalent Hyperlambda typique. Regardez-moi vous exécuter le code ci-dessous.
Les raisons de ce qui précède, bien sûr, sont que les modèles de conception O/RM sont fondamentalement incompatibles avec les systèmes RDBM. Simplement pour aucune autre raison que ce qui est considéré « les meilleures pratiques » lorsqu’il s’agit d’architecture et de conception de systèmes complexes, où l’on nous a appris pendant des décennies que nous sommes censés centrer nos conceptions autour d’un « OUI modèle », également lorsque nous créons nos couches d’accès à la base de données. Combinez cela avec les tentatives maladroites d’Entity Framework et d’autres bibliothèques O/RM d’essayer de créer une fonctionnalité de langage de programmation à typage statique dynamique et un suivi automatique des modifications apportées aux objets, pour ensuite « enregistrer » vos objets dans votre base de données, et vous avez la recette du désastre. Ce n’est pas vraiment la faute d’Entity Framework, mais plutôt une incompatibilité fondamentale avec votre SGBDR et votre modèle OO.
Sérieusement les enfants, O/RM sux ! Temps fort!
Étant donné que certains de mes lecteurs ont eu du mal à comprendre (et à y croire), j’ai créé un test qui mesure l’utilisation typique d’Entity Framework et le compare à l’utilisation typique d’Hyperlambda. Et même si Hyperlambda est de plusieurs ordres de grandeur plus lent que C#, le résultat final devient que Hyperlambda est en fait deux fois plus rapide que l’équivalent C#.
Les raisons des différences ci-dessus sont que tous les soi-disant « les meilleures pratiques » lié à l’OO et à l’accès aux données, combat essentiellement votre système RDBM, ce qui entraîne un code sous-optimal, où votre modèle OO devient le problème en lui-même. Et oui, je te comprends pouvez créer des solutions alternatives, mais si vous le faites, vous n’utilisez plus un modèle OO, mais utilisez sans doute votre O/RM de la même manière que vous utiliseriez Dapper ou Hyperlambda – À quel point votre bibliothèque O/RM est rendue 100% inutile . D’où, « optimisant » Entity Framework devient ici l’équivalent de ce que Microsoft a fait il y a 20 ans en « prouvé » que .Net et SQL Server étaient plus rapides que Java et Oracle.
Pour mémoire, sur l’histoire des benchmarks étranges, je me souviens que Don Box avait une fois une boucle for avec 10 000 itérations, créant un nouvel objet de chaîne C# à l’intérieur de la boucle, « prouver » que C# était plus rapide que C++. Bien sûr, le fait que le ramasse-miettes de son ordinateur ne se déclenche pas parce que le seuil de 10 000 était inférieur à la mémoire consommée avant le démarrage du GC, semblait être « hors du sujet » pour sa référence. En gros, ce à quoi je veux en venir, c’est que vous pouvez prouver tout ce que vous voulez prouver si vous essayez assez fort. J’ai cependant essayé d’être aussi fidèle à ce que j’ai vu dans l’utilisation réelle que j’ai fait mon test de performance, et ma conclusion est que Hyperlambda est deux fois plus rapide qu’Entity Framework pour toutes les préoccupations pratiques, à moins que vous ne fassiez un « animalerie » type de comparaison, ou un « Don Box » type de comparaison entre ces deux technologies différentes bien sûr…
En plus des différences de performances illustrées dans la vidéo ci-dessus, le projet O/RM typique ajoute également AutoMapper à la sauce spaghetti, ce qui fait que toute la raison pour laquelle vous avez utilisé un langage de programmation fortement typé en premier lieu est complètement jetée par la fenêtre, étant donné qu’AutoMapper élimine la vérification de type statique, en plus de cela, il complique votre code par ordres de grandeur, en dispersant votre logique liée à un seul concept, à travers plusieurs fichiers et parfois plusieurs projets, ce qui entraîne des dépendances supplémentaires, transformant efficacement votre langage de programmation fortement typé dans une grosse boule de boue – Tout en ajoutant généralement System.Reflection au mélange. Pour mémoire, la réflexion est environ 400 fois plus lente qu’une invocation de méthode virtuelle selon Jon Skeet. J’ai vu cela maintes et maintes fois. En fait, lorsqu’il s’agit d’Entity Framework, c’est le seul chose que j’ai vue.
J’ai eu un collègue qui a créé un pré-chargeur .Net Framework qui compilerait toutes les méthodes et classes de notre AppDomain, car chaque fois que nous créions une version de production, cela prenait 5 minutes avant que la chose ne devienne réactive en raison de déchets. architecture s’appuyant sur Entity Framework, Microsoft Workflow Foundation, des milliers de configurations AutoMapper et Dieu sait quoi de plus, dispersées autour d’un glorieux morceau de code spaghetti, impossible à réparer sans déchirer un nouveau trou dans la capacité de notre organisation à réellement gagner de l’argent. ..
Je me rends compte qu’il est possible de créer de meilleures solutions avec EF, mais je n’en ai jamais vu, simplement parce que c’est « si foutrement pratique » pour créer des solutions sous-optimales avec EF. Avec Magic et Hyperlambda, c’est presque impossible pour créer des solutions sous-optimales. En fait, les performances que vous voyez dans la vidéo ci-dessus peuvent être obtenues en cliquant sur un bouton, ce qui permet à Magic de créer automatiquement vos points de terminaison HTTP en 1 seconde, de sécuriser vos points de terminaison automatiquement, ce qui donne un code deux fois plus rapide que l’équivalent Entity Framework. Bien sûr, pour l’équivalent Entity Framework, vous passeriez généralement des jours, voire des semaines, simplement à envelopper la base de données Sakila avec des configurations AutoMapper, des modèles de vue, des modèles EF, DAL et de référentiel – De plus, Dieu sait quels autres modèles anti-conception vous avez. Je dois y ajouter avant d’obtenir une base « Bonjour la base de données » application en cours d’exécution … :/
Le paradoxe est que Hyperlambda est des ordres de grandeurs Ralentissez que C#. Pourtant, pour toutes les préoccupations pratiques, il finit par battre C# là où cela compte, c’est-à-dire la vitesse finale et l’évolutivité de votre application. Ci-dessous vous pouvez trouver mon code C#. Assurez-vous d’ajouter une référence à MySql.Data.EntityFrameworkCore de NuGet si vous souhaitez reproduire mon benchmark.
using System;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace BenchmarkEntityFramework
{
public class SakilaContext : DbContext
{
public DbSet<Actor> Actor { get; set; }
public DbSet<Film> Film { get; set; }
public DbSet<Language> Language { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseMySQL($"Server=localhost;Database=Sakila;Uid=root;Pwd=ThisIsNotAGoodPassword;SslMode=Preferred;Old Guids=true;");
}
public class Actor
{
[Key]
public int actor_id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public DateTime last_update { get; set; }
}
public class Film
{
[Key]
public int film_id { get; set; }
public string title { get; set; }
public string description { get; set; }
public int? release_year { get; set; }
[ForeignKey("language")]
public int language_id { get; set; }
[ForeignKey("original_language")]
public int? original_language_id { get; set; }
public virtual Language language { get; set; }
public virtual Language original_language { get; set; }
public int rental_duration { get; set; }
public decimal rental_rate { get; set; }
public int? length { get; set; }
public decimal replacement_cost { get; set; }
public string rating { get; set; }
public string special_features { get; set; }
public DateTime last_update { get; set; }
}
public class Language
{
[Key]
public int language_id { get; set; }
public string name { get; set; }
public DateTime last_update { get; set; }
}
public class EntityFrameworkController : ControllerBase
{
[HttpGet]
[Route("foo/bar1")]
public async Task<IActionResult> Get1(string firstNameStartsWith)
{
using (var context = new SakilaContext())
{
var result = context
.Actor
.Where(x => x.first_name.StartsWith(firstNameStartsWith))
.Take(25);
return new JsonResult(await result.ToListAsync());
}
}
[HttpGet]
[Route("foo/bar2")]
public async Task<IActionResult> Get2(string language)
{
using (var context = new SakilaContext())
{
var result = context
.Film
.Where(x => x.language.name == language)
.Take(25);
return new JsonResult(await result.ToListAsync());
}
}
[HttpGet]
[Route("foo/bar3")]
public async Task Get3()
{
for (var idx = 50; idx != 0; idx--)
{
using (var context = new SakilaContext())
{
var result = context
.Film
.Where(x => x.language.name == "English")
.Take(10);
await result.ToListAsync();
}
}
}
public class ChangeModel
{
public string description { get; set; }
}
[HttpPut]
[Route("foo/bar4")]
public async Task Update1([FromBody] ChangeModel input)
{
for (var idx = 1000; idx != 0; idx--)
{
using (var context = new SakilaContext())
{
var entity = await context
.Film
.Where(x => x.film_id == idx).FirstAsync();
entity.description = input.description + Guid.NewGuid().ToString();
await context.SaveChangesAsync();
}
}
}
}
}
Vous trouverez ci-dessous l’équivalent Hyperlambda.
multi-update.put.hl
.arguments
description:string
.no:int:1000
while
mt
get-value:x:@.no
.:int:0
.lambda
mysql.connect:[generic|sakila]
guid.new
convert:x:-
type:string
strings.concat
get-value:x:@.arguments/*/description
get-value:x:@convert
data.update
table:film
values
description:x:@strings.concat
where
and
film_id.eq:x:@.no
math.decrement:x:@.no
multi.get.hl
…