Синглтон - если или попробовать / поймать? - PullRequest
3 голосов
/ 13 сентября 2009

Я долго сидел над этой идеей и хотел бы услышать, что вы, ребята, думаете об этом.

Стандартная идиома написания синглтона примерно такова:

public class A {
...
   private static A _instance;

   public static A Instance() {
      if(_instance == null) {
          _instance = new A();
      }

      return _instance;
   }
...
}

Здесь я предлагаю другое решение:

public class A {
...
   private static A _instance;

   public static A Instance() {
       try {
         return _instance.Self();
       } catch(NullReferenceExceptio) {
         _instance = new A();
       }           

       return _instance.Self();
   }

   public A Self() {
       return this;
   }
...
}

Основная идея заключается в том, что затраты времени выполнения 1 разыменования и неисключенного исключения меньше, чем стоимость одной нулевой проверки. Я попытался измерить потенциальный прирост производительности и вот мои цифры:

Сон 1сек (пробовать / ловить) : 188788мс

Сон 1сек (проверка нуля) : 207485мс

И тестовый код:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;

public class A
{
   private static A _instance;
   public static A Instance() {
       try {
         return _instance.Self();
       } catch(NullReferenceException) {
         _instance = new A();
       }           

       return _instance.Self();
   }

   public A Self() {
       return this;
   }

    public void DoSomething()
    {
        Thread.Sleep(1);
    }
}

public class B
{
   private static B _instance;

   public static B Instance() {
      if(_instance == null) {
          _instance = new B();
      }

      return _instance;
   }

    public void DoSomething()
    {
        Thread.Sleep(1);
    }
}

public class MyClass
{
    public static void Main()
    {
        Stopwatch sw = new Stopwatch();
        sw.Reset();

        sw.Start();
        for(int i = 0; i < 100000; ++i) {
            A.Instance().DoSomething();
        }

        Console.WriteLine(sw.ElapsedMilliseconds);
        sw.Reset();

        sw.Start();
        for(int i = 0; i < 100000; ++i) {
            B.Instance().DoSomething();
        }
        Console.WriteLine(sw.ElapsedMilliseconds);

        RL();
    }

    #region Helper methods

    private static void WL(object text, params object[] args)
    {
        Console.WriteLine(text.ToString(), args);   
    }

    private static void RL()
    {
        Console.ReadLine(); 
    }

    private static void Break() 
    {
        System.Diagnostics.Debugger.Break();
    }

    #endregion
}

Результирующий прирост производительности составляет почти 10%, вопрос в том, является ли это микрооперацией, или она может предложить значительное повышение производительности для одноэлементных приложений (или для промежуточного программного обеспечения, например, журналирование)?

Ответы [ 8 ]

17 голосов
/ 13 сентября 2009

То, о чем вы спрашиваете, является наилучшим способом реализации плохого шаблона синглтона. Вы должны взглянуть на статью Джона Скита о , как реализовать шаблон синглтона в C # . Вы обнаружите, что есть гораздо лучшие (более безопасные) способы, и они не страдают от тех же проблем с производительностью.

7 голосов
/ 13 сентября 2009

Это ужасный способ сделать это. Как уже отмечали другие, исключения должны использоваться только для обработки исключительных, неожиданных ситуаций. И поскольку мы делаем это предположение, многие организации запускают свой код в контекстах, которые настойчиво ищут и сообщают об исключениях, даже обработанных , потому что обработанное исключение нулевой ссылки - почти наверняка ошибка . Если ваша программа неожиданно разыменовывает неверную память и продолжает веселиться, то есть вероятность, что в вашей программе что-то глубоко, сильно сломано, и это должно быть доведено до чьего-то внимания.

Не "плачь волк" и сознательно создавай ситуацию, которая выглядит ужасно разбитой, но на самом деле задуманной. Это просто делает больше работы для всех. Существует стандартный, прямой и общепринятый способ создания синглтона в C #; сделай это, если ты это имеешь в виду. Не пытайтесь придумать какую-нибудь сумасшедшую вещь, которая нарушает хорошие принципы программирования. Люди умнее меня спроектировали синглтонную реализацию, которая работает; глупо не использовать это.

Я научился этому нелегко. Короче говоря, я однажды сознательно использовал тест, который в большинстве случаев разыменовал бы плохую память в сценарии магистрали во время выполнения VBScript. Я позаботился о том, чтобы тщательно обработать исключение и правильно восстановиться, и команда ASP сошла с ума в тот день. Внезапно все их проверки целостности сервера начали сообщать, что огромное количество их страниц нарушало целостность памяти и восстанавливалось после этого. В итоге я реорганизовал реализацию этого сценария, чтобы делать только те пути кода, которые не приводили к исключениям.

Исключением с нулевыми ссылками всегда должна быть ошибка, точка. Когда вы обрабатываете исключение с нулевыми ссылками, вы скрываете ошибку.

Больше размышлений о классификации исключений:

http://ericlippert.com/2008/09/10/vexing-exceptions/

6 голосов
/ 13 сентября 2009

Я думаю, что если мое приложение будет вызывать конструктор синглтона достаточно часто, чтобы повышение производительности на 10% означало что-то, я бы сильно волновался.

Тем не менее, ни одна из версий не является поточно-ориентированной. Сначала сосредоточьтесь на том, чтобы делать такие вещи.

«Мы должны забыть о маленьких эффективность, скажем, около 97% время: преждевременная оптимизация корень зла. "

2 голосов
/ 13 сентября 2009

Эта оптимизация кажется тривиальной. Меня всегда учили использовать блоки try / catch только для отлова условий, которые трудно или невозможно проверить с помощью оператора if.

Это не значит, что предложенный вами подход не сработает. Это не намного лучше, чем оригинальный способ.

0 голосов
/ 13 сентября 2009

почему не просто:

public class A {
...
private static A _instance = new A();

public static A Instance() {
  return _instance;
}
...
}
0 голосов
/ 13 сентября 2009

Никогда не следует использовать обработку исключений для потока управления.

Кроме того, вместо:

 if(_instance == null) {
          _instance = new A();
      }

      return _instance;

Вы можете сделать:

return _instance ?? (_instance = new A());

Что семантически одинаково.

0 голосов
/ 13 сентября 2009

У «улучшенной» реализации есть большая проблема, которая заключается в том, что она запускает прерывание. Практическое правило заключается в том, что логика приложения не должна зависеть от срабатывания исключений.

0 голосов
/ 13 сентября 2009

Я думаю, что ваше решение вполне приемлемо, хотя я думаю, что есть несколько вещей, которые вы должны (и редко) рассматриваете, прежде чем внедрять ленивый загруженный синглтон-шаблон Панти.

  1. Не могли бы вы вместо этого предоставить версию DI, будет ли достаточно контейнера для единства с контрактом интерфейса? Это позволит вам позже поменять местами реализацию, а также значительно упростит тестирование.
  2. Если вы должны настаивать на использовании синглтона, вам действительно нужна реализация с отложенной загрузкой? Стоимость создания экземпляра в статическом конструкторе, неявно или явно, будет выполняться только тогда, когда на класс ссылаются во время выполнения, и, по моему опыту, почти на ВСЕ синглеты ссылаются только когда они хотят получить доступ к «Экземпляру» в любом случае.

Если вам нужно реализовать это так, как вы описали, вы бы сделали это следующим образом.

ОБНОВЛЕНИЕ : я обновил пример, чтобы вместо этого заблокировать тип класса вместо переменной блокировки, как Брайан Гидеон указывает, что экземпляр может быть в половинном инициализированном состоянии. Любое использование lock(typeof()) настоятельно не рекомендуется , и я рекомендую никогда не использовать этот подход.

public class A {
    private static A _instance;
    private A() { }
    public static A Instance {
        get {
            try {
                return _instance.Self();
            } catch (NullReferenceException) {
                lock (typeof(A)) {
                    if (_instance == null)
                        _instance = new A();
                }
            }
            return _instance.Self();
        }
    }
    private A Self() { return this; }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...