array
— сложный, гибкий, гибридный, сочетает в себе поведение list
и linked map
. Но мы используем его для всего, потому что PHP придерживается прагматичного подхода: иметь предельно правильный, здравый и реалистичный способ решения проблемы, исходящий из практических, а не теоретических рассуждений. array
позволяет делать работу, хотя о нем и так много рассказывают на лекциях по информатике. Но, к сожалению, с гибкостью приходит и сложность.Последний релиз PHP вызвал большое оживление в сообществе. Мы не могли дождаться того, чтобы начать использовать новые возможности и почувствовать вкус ~2х прироста производительности. Одна из причин, почему это случилось — структура
array
была переработана. Но массивы все также придерживаются принципа «оптимизировано для всего; оптимизировано для ничего», еще не все идеально, есть возможности для совершенствования.А что насчет структур данных SPL?К сожалению… они ужасны. Раньше, до PHP7, они предлагали _некоторые_ преимущества, но сейчас мы дошли до точки, когда использование SPL не имеет практического смысла.
Почему мы не можем просто поправить и улучшить их?Да, мы могли бы, но я считаю, что их дизайн и реализация настолько бедны, что лучше бы найти более современную замену.
«SPL data structures are horribly designed.»
— Anthony Ferrara
Введение:
php-ds
— расширение для PHP7, добавляющее структуры данных. Этот пост кратко охватывает поведение, производительность и преимущества каждой из них. Также в конце вы найдете список ответов на ожидаемые вопросы.Github: https://github.com/php-ds
Пространство имен:
Ds\
Интерфейсы:
Collection
, Sequence
, Hashable
Классы:
Vector
, Deque
, Stack
, Queue
, PriorityQueue
, Map
, Set
Collection (Коллекция)
Collection
— это базовый интерфейс, охватывающий общую функциональность: foreach
, echo
, count
, print_r
, var_dump
, serialize
, json_encode
, и clone
.Sequence (Последовательность)
Sequence
описывает поведение элементов, организованных в единый, линейный размер. В некоторых языках такая структура назвается List
(список). Подобен array
, который использует инкрементальные ключи, за исключением некоторых особенностей:- Значения всегда должны быть индексированы как
[0, 1, 2, …, size - 1]
- Извлечение или добавление приводит к обновление индекса всех последовательных значений
- Поддерживает доступ к значениям только из индекса
[0, size - 1]
Варианты использования
- Везде, где вы бы хотели использовать
array
как список (без ключей) - Более эффективная альтернатива
SplDoublyLinkedList
иSplFixedArray
Vector (Вектор)
Vector
представляет собой Sequence
, объединяющую значения в непрерывный буфер, увеличивающийся и уменьшающийся автоматически. Это наиболее эффективная последовательная структура данных, поскольку индекс элемента является прямым отражением его индекса в буфере, и увеличение вектора никак не повлияет на производительность.Сильные стороны
- Очень маленькое потребление памяти
- Очень быстрые итерации
get
,set
,push
иpop
имеют сложностьO(1)
Недостатки
insert
,remove
,shift
andunshift
имеют сложностьO(n)
Структурой номер один в Photoshop были Вектора.
—?Sean Parent, CppCon 2015
Deque (Двусвязная очередь)
Deque
(произносится как «deck») — это последовательность значений, объединенных в непрерывный буфер, увеличивающийся и уменьшающийся автоматически. Название является общепринятым сокращением от «double-ended queue». Используется внутри Ds\Queue
.Два указателя используется для отслеживания головы и хвоста. Наличие указателей позволяет изменять конец и начало буфера без необходимости перемещать другие элементы для освобождения места. Это делает
shift
и unshift
настолько быстрым, что даже Vector
не может конкурировать с этим.Доступ к значению по индексу требует вычисления соответствующей позиции в буфере:
((head + position) % capacity)
.Сильные стороны
- Очень маленькое потребление памяти
get
,set
,push
,pop
,shift
иunshift
имеют сложностьO(1)
Недостатки
insert
,remove
имеют сложностьO(n)
- Емкость буфера должна иметь степень двойки (
2?
)
Следующий бенчмарк показывает общее затраченное время и память, используемую для операции
push
2? случайных чисел. array
, Ds\Vector
и Ds\Deque
отрабатывают быстро, но SplDoublyLinkedList
стабильно показывает результат более чем в 2 раза хуже.SplDoublyLinkedList
выделяет память для каждого значения по отдельности, поэтому и происходит ожидаемый рост по памяти. array
и Ds\Deque
при своей реализации выделяют память порционно для поддержания достаточного объема для 2? элементов. Ds\Vector
имеет фактор роста 1.5, что влечет за собой увеличение количества выделений памяти, но меньший расход в целом.Следующий бенчмарк показывает время, затраченное на
unshift
единственного элемента в последовательности значений размером 2?. Время, требующееся на установку значений не учитывается.На графике видно, что
array_unshift
имеет сложность O(n)
: всякий раз, когда объем выборки удваивается, растет и время, необходимое для unshift
. Это объясняется тем, что каждый числовой показатель в диапазоне [1, size - 1]
должен быть обновлен.Но и
Ds\Vector::unshift
также O(n)
, так почему же он намного быстрее? Имейте ввиду, что array
хранит каждое значение в bucket
вместе с его ключем и хэшем. Поэтому приходится проверять каждый элемент и обновлять хэш, если индекс является числовым. На самом деле array_unshift
выделяет новый массив для этого и заменяет старый, когда все значения скопированы.В векторе же индекс значения — это прямое отображение его индекса в буфере, поэтому все, что нам нужно сделать — сдвинуть каждое значение в диапазоне [1, size — 1] вправо на одну позицию. Делается это при помощи всего одной операции
memmove
.Ds\Deque
и SplDoublyLinkedList
в свою очередь очень быстры, потому что на время для unshift
значения не влияет размер выборки, т.е. его сложность будет O(1)
.На следующем тесте видно сколько памяти используется при 2?
pop
операций. Другими словами при изменении размера от 2? до нуляИнтересно тут то, что
array
всегда держит выделенную память, даже если его размер существенно уменьшается. Ds\Vector
and Ds\Deque
позволяют в два раза уменьшить выделяемые ресурсы, если их размер падает ниже четверти своего текущего потенциала. SplDoublyLinkedList
освобождает память после каждого удаления из выборки, поэтому мы можем наблюдать линейное снижение.Stack (Стек)
Стек — является коллекцией, организованной по принципу «последним пришёл — первым вышел» или «LIFO» (last in — first out), позволяющей получить доступ только к значению на вершине структуры. Вы можете думать о нем как об оружейном магазине с динамической емкостью.
Ds\Stack
использует внутри себя Ds\Vector
.SplStack
наследуется от SplDoublyLinkedList, поэтому производительность будет эквивалентна сравнению Ds\Vector
to SplDoublyLinkedList
из предыдущих тестов. Посмотрим на время, необходимое для выполнения 2? pop
-операций, изменения размера от 2? до нуля.Queue (Очередь)
Очередь — тип данных с парадигмой доступа к элементам «первый пришел — первый вышел» («FIFO», «First In — First Out»). Такая коллекция позволяет получить доступ к элементам в порядке их добавления. Ее название говорит само за себя, представьте себе структуру как линию людей, стоящих в очереди на кассу в магазине.
Ds\Queue
использует внутри себя Ds\Deque
. SplQueue
наследуется от SplDoublyLinkedList
, поэтому производительность будет эквивалентна сравнению Ds\Deque
с SplDoublyLinkedList
, показанному в предыдущем бенчмарке.PriorityQueue (Очередь с приоритетом)
Очередь с приоритетом очень похожа на простую очередь. Элементы помещаются в очередь с указанным приоритетом и значение с наивысшим приоритетом всегда будет в передней части. Прямой перебор очереди с приоритетом очень деструктивен, это будет последовательный вызов операций
pop
, что является очень затратной операцией.Реализация очереди с приоритетом использует max-heap.
Принцип «первый пришел — первый вышел» сохраняется для значений с одинаковым приоритетом, так что группа значений с равным приоритетом можно рассматривать как обычную очередь.
А что же с производительностью? Следующий бенчмарк показывает время и память, требующиеся для операции
push
2? случайных чисел со случайным приоритетом в очередь. Те же случайные числа будут использоваться для каждого из тестов. В тесте для Queue
также генерируется случайный приоритет, хотя он и не используется.Это, наверное, самый значимый из всех бенчмарков.
Ds\PriorityQueue
работает более чем в два раза быстрее чем SplPriorityQueue
и использует только 5% от его памяти — это в 20 раз более эффективное решение по памяти.Но как? Как может получиться настолько большая разница, когда
SplPriorityQueue
использует аналогичную внутреннюю структуру? Все сводится к тому, как хранятся значения в паре с приоритетом. SplPriorityQueue
позволяет использовать любой тип значения для использования в качестве переменной, это приводит к тому, что в каждой паре приоритет занимает 32 байта.Ds\PriorityQueue
поддерживает только целочисленные приоритеты, поэтому каждой паре выделяется 24 байта. Но это все еще недостаточная разница для объяснения результата.Если вы посмотрите на исходный код
SplPriorityQueue::insert
, то заметите, что он инициализирует массив для хранения пары значение-приоритет.Т.к. массив имеет минимальную емкость 8, то для каждой пары на самом деле выделяется
zval + HashTable + 8 * (Bucket + hash) + 2 * zend_string + (8 + 16) byte string payloads
= 16 + 56 + 36 * 8 + 2 * 24 + 8 + 16
= 432 байта (64 бит).«Так… почему же массив?»
SplPriorityQueue
использует ту же внутреннюю структуру SplMaxHeap
, которая требует от значения быть типом zval
. Очевидный (но неэффективный) способ создания zval
-пары, т.к. zval
сам используется как array
.Hashable
Интерфейс, позволяющий объектам быть использованными в качестве ключей. Это альтернатива
spl_object_hash
, который детерминирует объект в хэш, базирующийся на его handle:
. Это означает, что два объекта, которые считались бы равными при сравнении, не имели бы равный хэш, т.к. они не являются одним и тем же экземпляром.Hashable
вводит только два метода: hash
и equals
. Многие другие языки поддерживают это изначально: в Java — hashCode
и equals
, или в Python ___hash___
и __eq__
. Было несколько RFC, добавляющих подобное поведение и в PHP, но ни один из не был принят.Все структуры, будут возвращать
spl_object_hash
, если ключи объектов, хранящиеся в них не реализуют в себе Hashable
.Структуры данных, работающие с интерфейсом
Hashable
: Map
и Set
.Map (Ассоциативный массив)
Map
является последовательной коллекцией пар ключ-значение, практически идентичной array
в аналогичном контексте. Ключи могут быть любого типа, единственное условие — уникальность. При повторном добавлении ключа значения заменяются.Как и в
array
, порядок вставки сохраняется.Сильные стороны
- Производительность и эффективность использования памяти практически идентичны
array
- Автоматическое освобождение памяти при уменьшении размера
- Ключи и значения могут быть любого типа, включая объекты
- Поддерживает работу с объектами, реализующими интерфейс
Hashable
put
,get
,remove
иcontainsKey
имеют сложностьO(1)
Недостатки
- Не может быть преобразован в
array
при наличии ключей-объектов - Нет возможности получить доступ к значениям по индексу (позиции)
Следующий бенчмарк показывает, что производительности и эффективности по памяти между
array
и Ds\Map
идентичны. Однако, array
всегда будет держать выделенную память, когда Ds\Map
, в свою очередь, освободит память при падении размера ниже четверти своего потенциала.Set (Множество)
Set
— коллекция уникальных значений. Учебники скажут вам, что в структуре Set
значения неупорядочены, если реализация не предусматривает иное. Возьмем для примера Java, java.util.Set
— это интерфейс с двумя основными реализациями: HashSet
и TreeSet
. HashSet
обеспечивает сложность O(1)
для add
и remove
, a TreeSet
обеспечивает сортированный набор данных, но сложность add
и remove
возрастает до O(log n)
.Set
использует ту же внутреннюю структуру, что и Map
, также основываясь на array
. Это означает, что Set
может быть отсортирован за время O(n * log(n))
когда это понадобится, в остальном он такой же простой как Map
и array
.Сильные стороны
add
,remove
иcontains
имеют сложностьO(1)
- Поддерживает работу с объектами, реализующими интерфейс
Hashable
- Поддерживает любой тип значений (
SplObjectStorage
поддерживает только объекты). - Имеет эквивалент поразрядных логических операций (
intersection
,difference
,union
,exclusive or
)
Недостатки
- Не поддерживает
push
,pop
,insert
,shift
илиunshift
get
имеет сложностьO(n)
если есть удаленные значения до момента индексации, в ином случае —O(1)
Следующий бенчмарк показывает время, затраченное на добавление 2? новых экземпляров
stdClass
. Он показывает, что Ds\Set
немного быстрее, чем SplObjectStorage
, и использует примерно в половину меньше памяти.Распространенным способом создания массива с уникальными значениями является
array_unique
, который создает новый array
, содержащий только уникальные значения. Но важно иметь ввиду, что значения в массиве не индексируются, in_array
является линейным поиском со сложность O(n)
. array_unique
работает только со значениями, без учета ключей, каждая проверка на наличие значения массива — линейный поиск, что даем нам в сумме сложность O(n?)
по времени и O(n)
по потреблению памяти.Ответы на ожидаемые вопросы и мнения
Есть ли тесты?
Сейчас около 2600 тестов. Вполне возможно, что некоторые тесты являются избыточными, но я предпочел бы косвенно проверить одну и ту же вещь дважды, чем не проверять совсем.
Документация? Справочник по API?
На момент написания этой статьи пока еще нет полной документации, но она появится вместе с первым стабильным релизом.
Однако, существуют некоторые хорошо документированные файлы-заглушки.
Можем ли мы посмотреть как устроены бенчмарки? Есть что-то о них?
Все бенчмарки прогонялись на стандартном билде
PHP 7.0.3
на 2015 Macbook Pro. Результаты могут отличаться в зависимости от версии и платформы.Почему Stack
, Queue
, Set
и Map
— не интерфейсы?
Я не верю, что есть необходимость в какой-либо альтернативной реализации. 3 интерфейса и 7 классов — это хороший баланс между прагматизмом и специализацией.
Когда мне использовать Deque
вместо Vector
?
Если вы точно знаете, что не будете использовать
shift
и unshift
, используйте Vector
. Для удобного тайпхинтинга можно указать в качестве типа Sequence
.Почему все классы являются финализированными?
Дизайн API
php-ds
применяет парадигму «Composition over inheritance.Структуры SPL являются хорошим примером того, как наследование может быть использовано не по назначение. Например,
SplStack
расширяет SplDoublyLinkedList
, который поддерживает произвольный доступ по индексу, shift
и unshift
— так что технически это не Стек.Фреймворк Java-коллекций также имеет несколько интересных случаев, когда наследование порождает двусмысленность.
ArrayDeque
имеет три метода добавления элементов: add
, addLast
и push
. Это не плохо, т.к. ArrayDeque
имплементирует Deque
и Queue
, что объясняет одновременное наличие addLast
и push
. Однако, все три метода сразу, делающие одно и тоже, вызывают путаницу и непоследовательность.Старый
java.util.Stack
расширял java.util.Vector
, тем самым заявляя, что „более полный и последовательный набор операций LIFO обеспечивается интерфейсом Deque
и его реализациями“, но Deque
включает в себя методы addFirst
и remove(x)
, которые не должны быть часть stack
структуры по API.Просто потому, что эти структуры имеют непересекающиеся методы не значит, что мы не можем так делать.
На самом деле, это справедливое замечание, но я по-прежнему считаю, что композиция больше подходит для построения структур данных. Они предназначены быть самодостаточными, подобно
array
. Вы не можете отнаследоваться от array
, он вынуждает вас разрабатывать собственные API вокруг себя, используя его только для хранения фактических данных.Наследование также вызвало бы лишние сложности во внутренней реализации.
Зачем нужен еще и ds
класс в глобальном пространстве имен?
Он обеспечивает альтернативный синтаксис:
Почему нет связного списка (Linked List)?
Класс
LinkedList
на самом деле появился первым, это казалось хорошим стартом. Но в итоге я решил удалить его, когда понял, что он не сможет конкурировать с Vector
или Deque
при любом раскладе. Две основные причины возможной поддержки: распределение накладных расходов и локальность ссылок.В связном списке мы добавляем или убираем зарезервированную память для элемента структуры (node) всякий раз, когда значение добавляется или удаляется. Нода содержит в себе два указателя (в случае с двусвязным списком), чтобы ссылаться на предыдущую и последующую ноды. Обе структуры,
Vector
и Deque
, выделяют буфер памяти заранее, поэтому нет необходимости делать это настолько часто. Они также не нуждаются в дополнительных указателях, чтобы знать какое значение до и какое после, тем самым снижаются накладные расходы.Будет ли связный список использовать меньше памяти, т.к. там нет буфера?
Только когда коллекция очень мала. Верхней границей количества памяти для
Vector
будет (1.5 * (size - 1)) * zval
байт, не менее *10 * zval*. В двусвязном списке же будет использоваться (size * (zval + 8 + 8))
. Поэтому связный список будет использовать меньше памяти, чем Vector
только тогда, когда его размер меньше 6 элементов.Окей… связный список использует больше памяти, но почему он медленный?
Узлы связного списка обладают плохой пространственной локальность. Это означает, что физическое расположение узла в памяти может быть далеко от прилегающих узлов. Таким образом итерации по связному списку скачут по памяти вместо использования кэша процессора. Значительное преимущество
Vector
и Deque
: элементы физически находятся рядом друг с другом.»Несмежность данных в структурах является корнем всех зол производительности. Конкретно, пожалуйста, скажите нет связным спискам"
«Нет почти ничего вреднее из того что вы можете сделать чтобы убить все плюсы современных микропроцессоров, чем использовать связный список»
— Chandler Carruth (CppCon 2014)
PHP — это язык для веб-разработки — производительность не важна.
Производительность не должна быть вашим главным приоритетом. Код должен быть последовательным, ремонтопригодным, надежным, предсказуемым, безопасным и легко понимаемым. Но это не означает, что производительность «не важна».
Мы тратим много времени, пытаясь уменьшить размер своих ассетов, делаем сравнительный анализ фреймворков и придумываем бессмысленные микро-оптимизации:
- print vs echo, which one is faster?
- The PHP Ternary Operator: Fast or not?
- The PHP Benchmark: setting the record straight
- Disproving the Single Quotes Performance Myth
Но в конечном итоге двухкратный прирост производительности, который приносит с собой PHP7 почему-то всех взбудоражил. Абсолютно для всех это — одно из главных преимуществ для перехода с PHP5.
Эффективный код позволяет снизить нагрузку на наши сервера. уменьшить время ответа наших API и веб-страниц и снижает время работы наших утилит для разработки. Высокая производительность важна, но поддерживаемость кода все же стоит во главе.
Обсуждения: Twitter, Reddit, Room 11
Исходный код: github.com/php-ds
Бенчмарки: github.com/php-ds/benchmarks
Комментарии (27)
MikeLP
28.03.2016 17:42+1Может я пропустил, но замерялась ли разница в скорости между екстеншином и полифилом.
iGusev
28.03.2016 17:47+4Они все будут по производительности как
array
, полифил скорее для тестов и подсветки в IDE предназначенRathil
29.03.2016 08:43+2Думаю больше не для тестов, а для тех мест, где действительно таки нет возможности поставить чеснок расширение. Для подсветки с головой бы хватило набор интерфейсов или пустых классов, как это делает пхпШторм для встроенных классов и функций.
Для тестов — хм, возможно, но если есть возможность поставить расширение, то зачем это юзать для тестов? Они (классы) все финализированны, а значит менять поведение не выйдет даже в тестах. Ну, честно меня...
KIVagant
28.03.2016 23:42+1Прекрасная статья, прекрасный перевод. С удовольствием прочитал и просмотрел все примеры. К сожалению (или к счастью), на текущем проекте нет нужды особо заморачиваться с производительностью php, но если вдруг придётся заниматься оптимизациями — буду иметь ввиду.
quantum
29.03.2016 02:35+1Вот еще слайды в тему статьи, мне кажется, будет полезным пролистать:
http://www.slideshare.net/patrick.allaert/php-data-structures-and-the-impact-of-php-7-on-them-php-days-2015
Rathil
29.03.2016 08:38+1Да, но все эти графики справедливы только тогда, когда вы берете именно расширение! Если брать реализацию на PHP, она даёт только общий интерфейс, но не этот прирост.
slonopotamus
29.03.2016 09:36-1PHP придерживается прагматичного подхода: иметь предельно правильный, здравый и реалистичный способ решения проблемы
Мы с вами в одном мире живем?
https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/Fesor
29.03.2016 12:29+3Это все мелочи. Да и...
- mysql_* — deprecated, removed in php7
- foreach ($foo as &$bar) — пофикшено в php7
- inconsistent: strpos, str_rot13 — это уде субъективизм, вы всегда можете завернуть это дело в красивую обертку
- no stack traces by default or for fatals, complex error reporting — пофикшено в php7, теперь на любой чих вываливается исключение
Короче половина статьи устарела, вторая половина применима не только к php. Если исходить из экономической эффективности — у php с этим все хорошо. Ну а то что его до 5-ой версии не проектировали вообще — ну это уже истарическая данность. Сейчас язык потихоньку чистят, хотя конечно же "идеальным" он никогда не будет.Punk_UnDeaD
29.03.2016 13:02foreach ($foo as &$bar) — пофикшено в php7
как-то пропустил это исправление
указатель освобождается в конце?Fesor
29.03.2016 13:30+2не указатель, а ссылка. Нет, не освобождается. Я с другим кейсом перепутал — модификация итерируемого массива по ссылке (например
unset($bar)
приводил к дырке в массиве, и сбивал указатель на текущий элемент. Теперь этого не происходит.
alexkunin
После слов «расширение для PHP7» хотел разочарованно закрыть, т.к. (лично у меня в моей практике) крайне редко есть возможность ставить нестандартные расширения.
Но зашел на github и увидел, что они предоставляют и PHP-only имплементацию: github.com/php-ds/polyfill. Здорово!
sl4mmer
Ну логично что у разработчиков нет возможности ставить что-то на продакшен-серверах, ни к чему хорошему это не приведет. Но по запросу админы должны же устанавливать необходимое ПО, если они игорируют или беcпричинно отклоняют такие запросы — надо как-то решать этот вопрос
alexkunin
"Дорогой GoDaddy! Можешь установить себе на шаред хостинг вот эту вот штуку? Это очень полезный модуль от сторонних разработчиков, нам он страшно нужен!"
"Wut?"
"Дорогой клиент! Выбранный тобою хостинг не позволяет использовать такой вот полезный модуль, а он нам страшно нужен! Не согласишься ли ты перейти на VPS, который в 5 раз дороже?"
"You're high or something?"
На самом деле (мои) клиенты часто уже на VPS или даже на дедикейтед, только почему-то там стоит Убунту 12.04 или (да!) 10.04. Просто взять и скомпилировать ПХП я не могу, т.к. редко проекты с 0, а экономически обосновать переход с ПХП 5.3 на 5.6 (или 7, как в этой статье) очень трудно. Один товарищ согласился перейти с джумлы 1.5 только когда его стали хакать каждый день.
И вот живой пример, прям перед глазами: IIS с PHP 5.2 (sic!), сайт для штата одной компании, написан на Qcodo подходящей версии, и этот фреймворк 1) давно умер 2) последняя версия хоть и поддерживает ПХП 5.4, но не является обратно-совместимой, т.е. нельзя просто взять и накатить, нужно будет править (а еще есть подозрение, что на живом сайте Qcodo немножко модифицирован). Клиент не готов вложиться апгрейд и/или переписывание (больше из-за времени, хотя деньги на непонятную ему фигню тоже не особо хочет отдавать). Так и живем.
iGusev
Может стоит сменить клиентов?
alexkunin
С чего бы? Платят отлично.
iGusev
Я за удовольствие от процесса работы)
alexkunin
Опен сорс к вашим (и моим) услугам. Кстати, "Лишь в ограничении познается мастер": до разумного предела, конечно, но так интересней.
VladimirAndreev
ну, если проект наглухо привязан к php 5.2, то в нем многое не заработает, даже если расширение написать как php-код...
alexkunin
В него вообще кроме старых PEAR-модулей ничего не засунешь. И какой-то небольшое процент композерских библиотек, которые PSR-0 используют.
IvanPanfilov
на VPS денги есть а на перенос php так нет
очень странно.
alexkunin
Там как раз было "нету денег на VPS, остаемся на шареде". Кстати, VPS в месяц сильно дешевле даже одного программиста в месяц (даже если это житель Индии или Пакистана).
grin
https://www.digitalocean.com/pricing/
от 5$ в месяц
alexkunin
И добавьте апгрейд со старой версии ПХП и другие накладные расходы. В случае, описанном выше в моем комментарии, это еще и Windows в качестве ОС (IIS в качестве веб-сервера), а также пара сервисов, написанных на C# с применением Excel через COM+. Не спрашивайте, откуда такой зоопарк. А клиента и его персонал нужно будет отучать заходить по RDP и делать кой-какие операции вручную, т.е. нужно будет им написать для этих операций Web-UI.
Но ни в коем случае не утверждаю, что это нормально. Такие проблемы нужно решать еще на этапе планирования (раскошелиться да нанять на неделю профессионала, а не индуса-студента), а также не оставлять без присмотра годами. Иначе в один прекрасный день окажется, что через месяц поддержка этой версии сервера Windows заканчивается, и теперь нужно срочно мигрировать на 2012, т.к. через месяц хостинг-провайдер уничтожит виртуальные сервера (так и случилось, еле успели).