Определенные оборотные стороны ко много-'small '-блокам?

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

19
задан Community 23 May 2017 в 12:26
поделиться

7 ответов

Я дам вам реальный пример, когда использование множества (очень) небольших сборок привело к созданию .Net DLL Hell.

На работе у нас есть большой самодельный фреймворк, который длинен до зубов (.Net 1.1). Помимо обычного встроенного кода фреймворка (включая ведение журнала, рабочий процесс, организацию очередей и т. Д.), Существовали также различные инкапсулированные сущности доступа к базе данных, типизированные наборы данных и некоторый другой код бизнес-логики. Я не участвовал в начальной разработке и последующем обслуживании этого фреймворка, но унаследовал его использование. Как я уже упоминал, вся эта структура привела к появлению множества небольших DLL. И, когда я говорю о многочисленных, мы имеем в виду более 100, а не 8 управляемых или около того, о которых вы упомянули. Еще больше усложняло ситуацию то, что все сборки были строго подписаны, версированы и должны появиться в GAC.

Итак, Перенесемся на несколько лет вперед и на несколько циклов обслуживания, и вот что получилось, так это то, что взаимозависимости между библиотеками DLL и поддерживаемыми ими приложениями нанесли ущерб. На каждой производственной машине есть огромный раздел перенаправления сборки в файле machine.config, который гарантирует, что «правильная» сборка будет загружена Fusion независимо от того, какая сборка запрошена. Это выросло из-за трудности, с которой столкнулись при перестройке каждой зависимой платформы и сборки приложения, которая зависела от той, которая была изменена или обновлена. Были предприняты большие усилия (обычно), чтобы гарантировать отсутствие критических изменений в сборках при их модификации. Сборки были перестроены, и в файл machine.config была внесена новая или обновленная запись.

Здесь ' Я сделаю паузу, чтобы послушать звук огромного коллективного стона и вздоха!

Этот конкретный сценарий является примером того, чего не следует делать. Ведь в этой ситуации вы попадаете в совершенно недопустимую ситуацию. Я помню, что мне потребовалось 2 дня, чтобы настроить мою машину для разработки в соответствии с этой структурой, когда я впервые начал работать с ней - устранение различий между моим GAC и GAC среды выполнения, перенаправления сборки machine.config, конфликты версий во время компиляции из-за неправильные ссылки или, что более вероятно, конфликт версий из-за прямой ссылки на компонент A и компонент B, но компонент B ссылался на компонент A, но на другую версию, чем прямая ссылка моего приложения. Вы уловили идею.

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

Можно подумать, что меня будут презирать, но я считаю, что для сборок существует логическое физическое разделение - и когда я говорю "сборки" здесь, Я предполагаю одну сборку для каждой DLL. Все сводится к взаимозависимостям. Если у меня есть сборка A, которая зависит от сборки B, я всегда спрашиваю себя, понадобится ли мне когда-нибудь ссылаться на сборку B без сборки A. Или есть ли польза от такого разделения. Как правило, хорошим индикатором является то, как ссылаются на сборки. Если вы должны были разделить свою большую библиотеку на сборки A, B, C, D и E. Если вы ссылались на сборку A в 90% случаев и из-за этого, вам всегда приходилось ссылаться на сборки B и C, потому что A зависел от них , то, вероятно, будет лучше объединить сборки A, B и C, если нет действительно убедительных аргументов, позволяющих им оставаться разделенными. Корпоративная библиотека - классический пример того, где вы Нам почти всегда приходилось ссылаться на 3 сборки, чтобы использовать один аспект библиотеки - однако в случае с Enterprise Library способность строить поверх основных функций и повторное использование кода является причиной ее архитектуры.

Еще один хороший ориентир - взглянуть на архитектуру. Если у вас хорошая четко составленная архитектура, где зависимости ваших сборок представляют собой стек, скажем, «вертикальный», в отличие от «сети», которая начинает формироваться, когда у вас есть зависимости во всех направлениях, тогда разделение сборок на функциональных границах имеет смысл. В противном случае постарайтесь объединить все воедино или пересмотреть архитектуру.

В любом случае, удачи!

возможность надстройки над основной функциональностью и повторное использование кода являются причиной его архитектуры.

Еще один хороший ориентир - взглянуть на архитектуру. Если у вас хорошая четко составленная архитектура, где зависимости ваших сборок представляют собой стек, скажем, «вертикальный», в отличие от «сети», которая начинает формироваться, когда у вас есть зависимости во всех направлениях, тогда разделение сборок на функциональных границах имеет смысл. В противном случае постарайтесь объединить все воедино или пересмотреть архитектуру.

В любом случае, удачи!

возможность надстройки над основной функциональностью и повторное использование кода являются причиной его архитектуры.

Еще один хороший ориентир - взглянуть на архитектуру. Если у вас хорошая четко составленная архитектура, где зависимости ваших сборок представляют собой стек, скажем, «вертикальный», в отличие от «сети», которая начинает формироваться, когда у вас есть зависимости во всех направлениях, тогда разделение сборок на функциональных границах имеет смысл. В противном случае постарайтесь объединить все воедино или пересмотреть архитектуру.

В любом случае, удачи!

который начинает формироваться, когда у вас есть зависимости во всех направлениях, тогда разделение сборок по функциональным границам имеет смысл. В противном случае постарайтесь объединить все воедино или пересмотреть архитектуру.

В любом случае, удачи!

который начинает формироваться, когда у вас есть зависимости во всех направлениях, тогда разделение сборок по функциональным границам имеет смысл. В противном случае постарайтесь объединить все воедино или пересмотреть архитектуру.

В любом случае, удачи!

14
ответ дан 30 November 2019 в 02:29
поделиться

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

Структура приложения :
Приложение, которое я создал, несколько абстрактно, потому что мне нужно было большое количество решений и проектов для тестирования, поэтому я написал код для их генерации для себя.

Приложение содержит 1000 классов, сгруппированных в 200 наборов по 5 классов, которые наследуются друг от друга. Классы называются Axxx, Bxxx, Cxxx, Dxxx и Exxx. Классы A полностью абстрактны, BD частично абстрактны, каждый из них переопределяет один из методов A, а E - конкретный. Эти методы реализованы так, что вызов одного метода в экземплярах E будет выполнять несколько вызовов вверх по цепочке иерархии. Все тела методов достаточно просты, чтобы теоретически все они были встроены.

Эти классы были распределены по сборкам в 8 различных конфигурациях по 2 измерениям:

  • Количество сборок: 10, 20, 50, 100
  • Направление резания: по иерархии наследования (ни один из AE никогда не находится в одной сборке вместе) и по иерархии наследования

Не все измерения точно измеряются; некоторые были сделаны по секундомеру и имеют большую погрешность. Выполнены следующие измерения:

  • Открытие решения в VS2008 (секундомер)
  • Компиляция решения (секундомер)
  • В IDE: время между запуском и первой выполненной строкой кода (секундомер)
  • В IDE: время для создания одного из Exxx для каждой из 200 групп в IDE (в коде)
  • В IDE: время выполнить 100 000 вызовов для каждого Exxx в IDE (в коде)
  • Последние три измерения «In IDE» , но из командной строки с использованием сборки Release

Результаты:
Открытие решения в VS2008

                               ----- in the IDE ------   ----- from prompt -----
Cut    Asm#   Open   Compile   Start   new()   Execute   Start   new()   Execute
Across   10    ~1s     ~2-3s       -   0.150    17.022       -   0.139    13.909
         20    ~1s       ~6s       -   0.152    17.753       -   0.132    13.997
         50    ~3s       15s   ~0.3s   0.153    17.119    0.2s   0.131    14.481
        100    ~6s       37s   ~0.5s   0.150    18.041    0.3s   0.132    14.478

Along    10    ~1s     ~2-3s       -   0.155    17.967       -   0.067    13.297
         20    ~1s       ~4s       -   0.145    17.318       -   0.065    13.268
         50    ~3s       12s   ~0.2s   0.146    17.888    0.2s   0.067    13.391
        100    ~6s       29s   ~0.5s   0.149    17.990    0.3s   0.067    13.415

Наблюдения:

  • Количество сборок (но не направление резания), кажется, имеет примерно линейное влияние на время, необходимое для открытия раствора. Это не кажется мне удивительным.
  • Примерно 6 секунд, время, необходимое для открытия решения, не кажется мне аргументом в пользу ограничения количества сборок. (Я не измерял, оказала ли привязка управления исходным кодом большое влияние на это время.)
  • Время компиляции увеличивается немного более чем линейно в этом измерении. Я полагаю, что большая часть этого связана с накладными расходами на сборку при компиляции, а не с разрешением символов между сборками. Я ожидал, что менее тривиальные сборки будут лучше масштабироваться по этой оси. Даже в этом случае я лично не считаю 30 секунд компиляции аргументом против разделения, особенно с учетом того, что в большинстве случаев только некоторые сборки будут нуждаться в повторной компиляции.
  • Кажется, есть еле измеримое, но заметное увеличение времени запуска. Первое, что делает приложение, - это выводит строку на консоль, время начала - это время, которое потребовалось для появления этой строки с момента начала выполнения (обратите внимание, что это оценки, потому что их было слишком быстро для точного измерения даже в худшем случае) .
  • Интересно, что загрузка сборки вне среды IDE (очень немного) более эффективна, чем внутри среды IDE. Вероятно, это как-то связано с попыткой подключить отладчик или что-то в этом роде.
  • Также обратите внимание, что повторный запуск приложения вне IDE еще больше сократил время запуска в худшем случае. Возможны сценарии, в которых 0,3 с для запуска неприемлемы, но я не могу себе представить, что это будет иметь значение для многих мест.
  • Время инициализации и выполнения внутри IDE стабильно, независимо от разделения сборки; это может быть связано с тем, что ему требуется отладка, из-за чего ему легче разрешать символы в сборках.
  • За пределами IDE эта стабильность сохраняется, с одной оговоркой ... количество сборок не имеет значения для выполнения, но при разрезании по иерархии наследования время выполнения немного хуже, чем при разрезании по . Заметьте, что разница кажется мне слишком маленькой, чтобы быть системной; это, вероятно, дополнительное время, которое требуется во время выполнения один раз, чтобы выяснить, как сделать ту же оптимизацию ... честно говоря, хотя я мог бы исследовать это дальше, различия настолько малы, что я не склонен сильно беспокоиться.

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

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

30
ответ дан 30 November 2019 в 02:29
поделиться

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

Подход, который я пытаюсь использовать, заключается в следующем: Пространства имен предназначены для логической организации. Сборки предназначены для группировки классов / пространств имен, которые должны физически использоваться вместе. Т.е. если вы не ожидаете, что вам понадобится ClassA, а не ClassB (или наоборот), они принадлежат одной сборке.

4
ответ дан 30 November 2019 в 02:29
поделиться

Лично мне нравится монолитный подход.

Но иногда вы не можете помочь создать больше сборок. За это обычно отвечает .NET Remoting, когда вам требуется сборка общего интерфейса.

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

0
ответ дан 30 November 2019 в 02:29
поделиться

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

Обратной стороной большого количества проектов является то, что ( по крайней мере, в VS) для компиляции требуется довольно много времени по сравнению с несколькими проектами.

2
ответ дан 30 November 2019 в 02:29
поделиться

Самым большим фактором в вашей сборочной организации должен быть ваш граф зависимостей как на уровне класса, так и на уровне сборки.

Сборки не должны иметь циклических ссылок. Это должно быть довольно очевидно для начала.

Классы, которые больше всего зависят друг от друга, должны быть в одной сборке.

Если класс A зависит от класса B, а B может не зависеть напрямую от A, маловероятно, что он когда-либо будет использоваться отдельно от A, тогда они должны совместно использовать сборку.

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

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

Возможно, один из более важные вещи, для которых вы можете использовать сборки, - это различать общедоступные API-интерфейсы и объекты, используемые внутри, для обеспечения работы общедоступных API-интерфейсов. Помещая API в отдельную сборку, вы можете обеспечить непрозрачность его API.

и объекты, используемые внутри для обеспечения работы общедоступных API. Помещая API в отдельную сборку, вы можете обеспечить непрозрачность его API.

и объекты, используемые внутри для обеспечения работы общедоступных API. Помещая API в отдельную сборку, вы можете обеспечить непрозрачность его API.

2
ответ дан 30 November 2019 в 02:29
поделиться

Думаю, если вы говорите только о дюжине, все будет в порядке. Я работаю над приложением со 100+ сборками, и это очень больно.

Если у вас нет способа управления зависимостями - зная, что выйдет из строя, если вы измените сборку X, у вас проблемы.

Одна «приятная» проблема, с которой я столкнулся, - это когда сборка A ссылается сборки B, C и B ссылаются на V1 сборки D, а C ссылаются на V2 сборки D. («Скрученный ромб» Думаю, ответ на ваш вопрос очень сильно зависит от семантики ваших сборок. Могут ли разные приложения использовать одну сборку? Хотите иметь возможность обновлять сборки для обоих приложений по отдельности? Вы собираетесь использовать GAC? Или скопировать сборки рядом с исполняемыми?

1
ответ дан 30 November 2019 в 02:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: