mercredi 8 octobre 2025

Q/A Conception et Développement



🔹 1. Comment mesurer et optimiser les performances d’une application en C# ?

✅ Mesure :

  • Profiler : Visual Studio Profiler, JetBrains dotTrace, PerfView

  • Benchmarking : BenchmarkDotNet pour mesurer des micro-benchmarks

  • EventCounters / Diagnostics pour mesurer l’usage CPU, GC, allocations mémoire

  • Stopwatch pour tests simples :

var sw = Stopwatch.StartNew();
// code à mesurer
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

✅ Optimisation :

  1. Réduire les allocations GC inutiles (string immutables → StringBuilder, Span<T>/Memory<T>)

  2. Préférer ValueTuple à Tuple pour éviter les heap allocations

  3. Minimiser l’usage de boxing/unboxing

  4. Optimiser LINQ critiques (éviter Select().ToList() répétitif)

  5. Profiler et cibler les hot paths

  6. Utiliser async/await pour IO bound et Parallel/Task pour CPU bound


🔹 2. Avantages et inconvénients des structs par rapport aux classes

Critère Struct Classe
Type Value type Reference type
Allocation Stack (rapide) Heap (GC)
Taille Petit (<16 octets recommandé) Illimitée
Mutation Copie sur assignation Référence partagée
Héritage Pas de polymorphisme Supporté
Avantage Pas de GC, performances Flexibilité, héritage
Inconvénient Copie fréquente peut être coûteuse Allocations sur heap

✅ Bon usage : types petits et immuables (ex: Point, Color, DateTime)


🔹 3. Pourquoi faut-il éviter les boxing et unboxing ?

  • Boxing : conversion d’un value type en object/interface → allocation sur heap

  • Unboxing : cast inverse → vérification type → overhead CPU

🧩 Exemple :

int x = 5;
object obj = x;      // boxing
int y = (int)obj;    // unboxing
  • Impacts : GC supplémentaire, perte performance

  • Solution : utiliser generics (List<int> au lieu de ArrayList)


🔹 4. Différence entre static class et sealed class

Critère static class sealed class
Instanciation Impossible Possible
Héritage Impossible Impossible à hériter
Membres Tous statiques Membres normaux ou statiques
Usage Helper, extension methods Classe normale qu’on veut empêcher d’hériter

🔹 5. Fonctionnement du pattern Decorator en C#

✅ Objectif :

Ajouter des fonctionnalités dynamiquement à un objet sans modifier sa classe.

🧩 Exemple :

public interface INotifier { void Send(string message); }
public class EmailNotifier : INotifier { public void Send(string msg) => Console.WriteLine("Email: " + msg); }

public class SmsDecorator : INotifier
{
    private readonly INotifier _inner;
    public SmsDecorator(INotifier inner) => _inner = inner;
    public void Send(string msg)
    {
        _inner.Send(msg);
        Console.WriteLine("SMS: " + msg);
    }
}

// Usage
INotifier notifier = new SmsDecorator(new EmailNotifier());
notifier.Send("Hello");

🔹 6. Différences entre abstract class et interface

Critère Abstract class Interface
Héritage Simple/multiple avec interface Multiple
Membres Méthodes, propriétés, champs, constructeurs Méthodes/propriétés/events seulement
Implémentation Partielle possible Par défaut depuis C# 8 avec default impl.
Usage Base commune avec comportement partagé Contrat pur, flexible

🔹 7. Fonctionnement du pattern Singleton et pièges en C#

✅ Objectif :

Assurer une unique instance globale

🧩 Exemple thread-safe :

public class Singleton
{
    private static readonly Lazy<Singleton> _instance = new(() => new Singleton());
    public static Singleton Instance => _instance.Value;
    private Singleton() { }
}

⚠️ Pièges :

  • Non thread-safe si static sans Lazy

  • Serialization peut créer de nouvelles instances

  • Reflection peut bypasser le constructeur privé


🔹 8. Pourquoi utiliser lock(obj) plutôt que Monitor.Enter(obj) ?

  • lock(obj) = syntactic sugar pour Monitor.Enter/Exit avec gestion automatique des exceptions

  • Monitor.Enter/Exit = plus flexible, mais risque de deadlock si Exit oublié

lock(_lockObj) { /* code thread-safe */ }

lock est recommandé pour la plupart des cas simples.


🔹 9. Fonctionnement du pattern Repository et utilité

✅ Objectif :

Abstraction de la couche accès données pour séparer logique métier et DB

🧩 Exemple :

public interface IProductRepository
{
    Product GetById(int id);
    void Add(Product p);
}

public class ProductRepository : IProductRepository
{
    private readonly DbContext _context;
    public ProductRepository(DbContext context) => _context = context;

    public Product GetById(int id) => _context.Products.Find(id);
    public void Add(Product p) => _context.Products.Add(p);
}
  • Facilite le unit testing

  • Permet de changer facilement la source de données


🔹 10. Fonctionnement de yield return et utilité

✅ Objectif :

Créer des itérateurs paresseux (lazy evaluation)

🧩 Exemple :

IEnumerable<int> GetNumbers()
{
    for (int i = 0; i < 5; i++)
        yield return i;
}

foreach(var n in GetNumbers()) Console.WriteLine(n);

✅ Avantages :

  • Mémoire optimisée (pas de liste complète)

  • Evaluation à la demande

  • Idéal pour gros jeux de données ou flux infinis


📘 Synthèse des concepts avancés

Concept Utilité / Avantage Astuces / Pièges
Performance Profiling, BenchmarkDotNet, Span/Memory Eviter allocations, LINQ sur-alloué
Struct vs Class Stack allocation, pas de GC Copier grands structs coûteux
Boxing/Unboxing Éviter allocations heap Utiliser Generics
static vs sealed Helper vs classe finale Comprendre héritage
Decorator Extensibilité dynamique Pas toucher code existant
abstract vs interface Base avec comportement vs contrat pur Choisir selon réutilisation
Singleton Unique instance Thread-safe + Lazy + attention serialization/reflection
lock vs Monitor Thread safety simple lock recommandé
Repository Isolation DB layer Testabilité, flexibilité
yield return Lazy iteration Evite création complète de liste





🔹 1. Gérer la latence et les performances dans une application de trading en temps réel

✅ Bonnes pratiques :

  1. Minimiser les allocations mémoire :

    • Utiliser Span<T>, Memory<T>, ArrayPool<T> pour éviter la pression sur le GC.

  2. Eviter le boxing/unboxing :

    • Utiliser des struct et des ValueTuple pour manipuler des données financières.

  3. Optimiser les structures de données :

    • ConcurrentDictionary, ImmutableDictionary, PriorityQueue pour accès rapide et thread-safe.

  4. Parallélisme contrôlé :

    • Distribuer les calculs sur plusieurs threads sans saturer le CPU.

  5. Priorité thread :

    • Utiliser ThreadPriority.Highest pour threads critiques (ex: matching engine).

  6. Low-latency networking :

    • Préférer Span<byte> pour buffers réseau, SocketAsyncEventArgs pour IO non-bloquant.


🔹 2. Meilleures pratiques pour développer des applications temps réel en C#

  • Éviter la GC sur hot path (pré-allocation, objets immuables)

  • Traitement parallèle : Task, Parallel.ForEach, mais attention à la contention

  • Thread affinity : conserver les threads critiques pour réduire les migrations

  • Lock-free structures : ConcurrentQueue<T>, ConcurrentDictionary<T>, Interlocked pour éviter les locks coûteux

  • Profiling et benchmarks : mesurer la latence et le throughput en continu

  • Logging asynchrone : ne pas bloquer le thread critique


🔹 3. Garbage Collector et impact sur le pricing temps réel

  • GC générationnel :

    • Gen0 : objets temporaires (allocation rapide)

    • Gen1 : objets intermédiaires

    • Gen2 : objets long-lived

  • Impact :

    • Une pause GC peut introduire des latences inacceptables pour le trading haute fréquence.

  • Solutions :

    • Utiliser structs, Span<T>, pools (ArrayPool<T>), éviter allocations sur hot path

    • GC.TryStartNoGCRegion() pour sections critiques


🔹 4. Différences : Task.Run(), Parallel.ForEach(), ThreadPool.QueueUserWorkItem()

Méthode Description Usage
Task.Run() Crée une Task sur le thread pool Pour async/await et calcul CPU-bound
Parallel.ForEach() Boucle parallèle avec partitioning Traitement batch de données de marché
ThreadPool.QueueUserWorkItem() Exécute un delegate sur un thread pool Très bas niveau, sans Task

✅ Choix dépend du niveau d’abstraction et du contrôle sur la synchronisation.


🔹 5. Optimiser les accès concurrents aux données financières

  • Concurrent collections : ConcurrentDictionary, ConcurrentQueue, ConcurrentBag

  • ReaderWriterLockSlim : lecture fréquente, écriture rare

  • Lock-free programming : Interlocked.Increment, Volatile.Read/Write

  • Immutable structures : lecture thread-safe sans verrou


🔹 6. Communication efficace entre moteur de risque et UI en temps réel

  • Event-driven architecture :

    • Le moteur de risque émet des événements (PriceChanged, RiskUpdated)

    • UI s’abonne via IObservable<T> ou Reactive Extensions (Rx.NET)

  • Queues : ConcurrentQueue<T> ou BlockingCollection<T> pour bufferiser les updates

  • WebSocket / SignalR : pour diffusion asynchrone et bidirectionnelle


🔹 7. Utilisation des WebSockets en C#

  • Rôle : diffusion continue de flux de marché (bid/ask, trades)

  • Avantages : faible latence, full-duplex, léger

  • Exemple serveur C# :

var webSocket = await context.WebSockets.AcceptWebSocketAsync();
while(webSocket.State == WebSocketState.Open)
{
    var buffer = new byte[1024];
    var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
    // traiter message
}
  • Exemple client : ClientWebSocket pour s’abonner aux flux de prix


🔹 8. Assurer la fiabilité et haute disponibilité

  • Redondance : clusters pour le moteur de trading

  • Retry/Failover : circuit breaker et backoff

  • Snapshots / persistence : journaux des transactions et state recovery

  • Monitoring & alerting : Latency, GC pause, exceptions


🔹 9. Memory-Mapped Files pour stocker des données de marché

  • Permet d’accéder à un fichier comme une mémoire partagée

  • Avantages :

    • Très faible latence (pas de parsing répétitif)

    • Partage entre processus

    • Lecture/écriture rapide sur gros volumes

  • Exemple :

using (var mmf = MemoryMappedFile.CreateFromFile("market.dat", FileMode.OpenOrCreate, "map", 1024))
{
    using var accessor = mmf.CreateViewAccessor();
    accessor.Write(0, 42);
    int value = accessor.ReadInt32(0);
}

🔹 10. Implémenter un cache performant pour l’accès aux données de marché

  • Objectif : éviter d’interroger les sources externes à chaque requête

  • Techniques :

    • MemoryCache avec MemoryCacheEntryOptions

    • ConcurrentDictionary avec expiration personnalisée

    • LRU Cache / cache basé sur LinkedList + Dictionary pour taille fixe

    • Préférer readonly structures pour thread-safety

  • Exemple simple avec MemoryCache :

var cache = new MemoryCache(new MemoryCacheOptions());
cache.Set("AAPL", 150.23, TimeSpan.FromSeconds(1));
double price = (double)cache.Get("AAPL");

📘 Résumé des meilleures pratiques pour C# temps réel (Trading / Pricing)

Concept Astuce
Allocation mémoire Span<T>, Memory<T>, ArrayPool<T>
Threading Task.Run, Parallel.ForEach, ThreadPool, lock-free
Concurrence ConcurrentDictionary, ReaderWriterLockSlim, Immutable structures
UI communication Rx.NET, ObservableCollection, WebSockets
Haute disponibilité Clustering, snapshots, failover, monitoring
Cache MemoryCache, LRU, readonly structures
Low-latency I/O MemoryMappedFile, SocketAsyncEventArgs
Garbage Collector Minimiser allocations, utiliser NoGCRegion si critique


Aucun commentaire:

Enregistrer un commentaire