Примечание: Я мог выбрать неправильное слово в заголовке; возможно, я действительно говорю здесь о полиномиальном росте. См. Результат теста в конце этого вопроса.
Начнем с этих трех рекурсивных универсальных интерфейсов † , которые представляют неизменяемые стеки:
interface IStack<T>
{
INonEmptyStack<T, IStack<T>> Push(T x);
}
interface IEmptyStack<T> : IStack<T>
{
new INonEmptyStack<T, IEmptyStack<T>> Push(T x);
}
interface INonEmptyStack<T, out TStackBeneath> : IStack<T>
where TStackBeneath : IStack<T>
{
T Top { get; }
TStackBeneath Pop();
new INonEmptyStack<T, INonEmptyStack<T, TStackBeneath>> Push(T x);
}
Я создал простые реализации EmptyStack
, NonEmptyStack
.
Обновление №1: См. Код ниже.
Я заметил следующие особенности их производительности во время выполнения:
EmptyStack
в первый раз занимает более 7 секунд. EmptyStack
после этого практически не занимает времени . Обновление №2:
Наконец-то я провел более точное измерение. См. Тестовый код и результаты ниже.
Я обнаружил только во время этих тестов, что .NET 3.5, похоже, не допускает универсальных типов с глубиной рекурсии ≥ 100. В .NET 4, похоже, нет этого ограничения.
Первые два факта заставляют меня подозревать, что низкая производительность связана не с моей реализацией, а скорее с системой типов: .NET должен создать 1000 различных закрытых универсальных типов , то есть:
] EmptyStack
NonEmptyStack >
NonEmptyStack >>
NonEmptyStack >>>
Вопросы:
T
, T >
, T >>
и т.д. становятся экспоненциально тем медленнее, чем глубже они вложены? † ) Сноска не по теме: эти типы весьма интересны, кстати . потому что они позволяют компилятору обнаруживать определенные ошибки, такие как:
stack.Push (item) .Pop (). Pop (); // ^^^^^^ // вызывает ошибку времени компиляции, если известно, что «стек» непустой.
Или вы можете выразить требования для определенных операций со стеком:
TStackBeneath PopTwoItems
(Стек INonEmptyStack )
internal class EmptyStack<T> : IEmptyStack<T>
{
public INonEmptyStack<T, IEmptyStack<T>> Push(T x)
{
return new NonEmptyStack<T, IEmptyStack<T>>(x, this);
}
INonEmptyStack<T, IStack<T>> IStack<T>.Push(T x)
{
return Push(x);
}
}
// ^ this could be made into a singleton per type T
internal class NonEmptyStack<T, TStackBeneath> : INonEmptyStack<T, TStackBeneath>
where TStackBeneath : IStack<T>
{
private readonly T top;
private readonly TStackBeneath stackBeneathTop;
public NonEmptyStack(T top, TStackBeneath stackBeneathTop)
{
this.top = top;
this.stackBeneathTop = stackBeneathTop;
}
public T Top { get { return top; } }
public TStackBeneath Pop()
{
return stackBeneathTop;
}
public INonEmptyStack<T, INonEmptyStack<T, TStackBeneath>> Push(T x)
{
return new NonEmptyStack<T, INonEmptyStack<T, TStackBeneath>>(x, this);
}
INonEmptyStack<T, IStack<T>> IStack<T>.Push(T x)
{
return Push(x);
}
}
Я использовал следующий код для измерения времени создания экземпляров рекурсивного универсального типа для .NET 4 на Windows 7 SP 1 x64 ( Intel U4100 @ 1,3 ГГц, 4 ГБ ОЗУ) ноутбук. Это другая, более быстрая машина, чем та, которую я изначально использовал, поэтому результаты не совпадают с приведенными выше утверждениями.
Console.WriteLine("N, t [ms]");
int outerN = 0;
while (true)
{
outerN++;
var appDomain = AppDomain.CreateDomain(outerN.ToString());
appDomain.SetData("n", outerN);
appDomain.DoCallBack(delegate {
int n = (int)AppDomain.CurrentDomain.GetData("n");
var stopwatch = new Stopwatch();
stopwatch.Start();
IStack<int> s = new EmptyStack<int>();
for (int i = 0; i < n; ++i)
{
s = s.Push(i); // <-- this "creates" a new type
}
stopwatch.Stop();
long ms = stopwatch.ElapsedMilliseconds;
Console.WriteLine("{0}, {1}", n, ms);
});
AppDomain.Unload(appDomain);
}
(Каждое измерение проводится в отдельном домене приложения, потому что это гарантирует, что все типы среды выполнения должны быть повторно -создается в каждой итерации цикла.)
Вот график XY выходных данных:
Горизонтальная ось: N обозначает глубину рекурсии типов, то есть:
NonEmptyStack >
NonEmptyStack >>
Вертикальная ось: t - время (в миллисекундах), необходимое для помещения N целых чисел в стек. (Время, необходимое для создания типов среды выполнения, если это действительно произойдет, включено в это измерение.)