Лучше ли работать с конкретным типом, а не с интерфейсом
Я столкнулся с некоторыми правилами (рекомендациями), чтобы использовать конкретный список и словарь, а не IList и IDictionary, учитывая образцы тестов, которые показывают, что через интерфейс он немного медленнее. Например, добавив 10000 значений в список, а затем сделав подсчет в списке 1 миллиард раз, это означает, что выполнение этого через интерфейс в 28 раз медленнее, чем через конкретный класс. т.е. через конкретный класс он занимает 80 мс, через интерфейс требуется 2800 мс, что показывает, как очень медленно это происходит через интерфейс. Учитывая это, разумно использовать конкретный класс. Есть ли причина, по которой интерфейс настолько медленнее (вероятно, больше направлен на кого-то, кто знает больше о внутренностях .net).
спасибо
Скотт
Ответы
Ответ 1
Я думаю, что это совершенно очевидно, если вы посмотрите на разборку:
Версия IList
скомпилирована для:
for (int i = 0; i < 1000000000; i++)
0000003d xor edi,edi
{
count = lst.Count;
0000003f mov ecx,esi
00000041 call dword ptr ds:[00280024h]
00000047 mov ebx,eax
for (int i = 0; i < 1000000000; i++)
00000049 inc edi
0000004a cmp edi,3B9ACA00h
00000050 jl 0000003F
}
Доступ к IList.Count
скомпилирован в инструкцию call
.
Версия List
с другой стороны встроена:
for (int i = 0; i < 1000000000; i++)
0000003a xor edx,edx
0000003c mov eax,dword ptr [esi+0Ch]
0000003f mov esi,eax
00000041 inc edx
00000042 cmp edx,3B9ACA00h
00000048 jl 0000003F
}
Нет call
здесь. Просто команда mov, inc, cmp и jl в цикле. Конечно, это быстрее.
Но помните: обычно вы делаете что-то с содержимым вашего списка, вы не просто перебираете его. Обычно это занимает намного больше времени, чем один вызов функции, поэтому вызовы методов интерфейса редко вызывают проблемы с производительностью.
Ответ 2
Основной причиной использования интерфейсов является гибкость, разделение проблем и т.д.
Поэтому я бы по-прежнему советовал использовать интерфейсы в большинстве случаев (не все, где это необходимо), и только подумайте о переходе на конкретные классы при возникновении реальной проблемы с производительностью.
И после запуска теста без GC.Collect()
и в режиме Release я получаю 96 и 560 мс. Значительно меньшая разница.
Ответ 3
Ваши тесты производительности явно ошибочны. Вы тестируете много разных вещей. Прежде всего GC.Collect
запускает поток финализатора, который влияет на производительность всего, что работает после него. Затем вы не только проверяете время, затрачиваемое на вызов методов интерфейса, но и много времени на копирование данных на новые массивы (с тех пор, как вы создаете большие массивы) - и их сбор! - поэтому разница между вызовами интерфейса и вызовы экземпляров полностью потеряются.
Вот тест: я тестировал необработанные служебные служебные вызовы интерфейсных вызовов. При запуске в режиме выпуска вне Visual Studio:
public interface IMyInterface
{
void InterfaceMethod();
}
public class MyClass : IMyInterface
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void InterfaceMethod()
{
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void InstanceMethod()
{
}
}
class Program
{
static void Main(string[] args)
{
// JITting everyting:
MyClass c = new MyClass();
c.InstanceMethod();
c.InterfaceMethod();
TestInterface(c, 1);
TestConcrete(c, 1);
Stopwatch watch = Stopwatch.StartNew();
watch.Start();
var x = watch.ElapsedMilliseconds;
// Starting tests:
watch = Stopwatch.StartNew();
TestInterface(c, Int32.MaxValue - 2);
var ms = watch.ElapsedMilliseconds;
Console.WriteLine("Interface: " + ms);
watch = Stopwatch.StartNew();
TestConcrete(c, Int32.MaxValue - 2);
ms = watch.ElapsedMilliseconds;
Console.WriteLine("Concrete: " + ms);
}
static void TestInterface(IMyInterface iface, int iterations)
{
for (int i = 0; i < iterations; i++)
{
iface.InterfaceMethod();
}
}
static void TestConcrete(MyClass c, int iterations)
{
for (int i = 0; i < iterations; i++)
{
c.InstanceMethod();
}
}
}
Вывод:
Interface: 4861
Concrete: 4236
Ответ 4
Это действительно проблема в вашем приложении, интерфейсы могут сделать код лучше и проще для повторного использования/поддержки
Если вам действительно нужно улучшить производительность, попробуйте сначала улучшить алгоритм, например, вам действительно нужно подсчитать количество элементов в 1 миллиард раз? вы не можете хранить счет где-то и иметь какой-то флаг, указывающий, что элементы были изменены, и вам нужно пересчитать его?
При этом вопрос Влияние производительности на общие интерфейсы указывает на производительность интерфейсов
Ответ 5
Не было очевидной разницы в производительности в неоптимизированном режиме DEBUG и примерно на 35-40% в режиме RELEASE с .Net 3.5, Visual Stusio 2008.
Debug:
List test, ms: 1234.375
IList test, ms: 1218.75
Release:
List test, ms: 609.375
IList test, ms: 968.75
Тестовый код:
List<int> list = new List<int>();
var start = DateTime.Now;
for (int i = 0; i < 50000000; i++) list.Add(i);
for (int i = 0; i < 50000000; i++) list[i] = 0;
var span = DateTime.Now - start;
Console.WriteLine("List test, ms: {0}", span.TotalMilliseconds);
IList<int> ilist = new List<int>();
start = DateTime.Now;
for (int i = 0; i < 50000000; i++) ilist.Add(i);
for (int i = 0; i < 50000000; i++) ilist[i] = 0;
span = DateTime.Now - start;
Console.WriteLine("IList test, ms: {0}", span.TotalMilliseconds);
Ответ 6
Это тестовый код, который я использовал, чтобы увидеть различия:
using System;
using System.Collections.Generic;
using System.Diagnostics;
public class test1
{
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
const int NUM_ITEMS = 10000;
const int NUM_LOOPS2 = 1000000000;
List<int> lst = new List<int>(NUM_ITEMS);
IList<int> ilst = lst;
for (int i = 0; i < NUM_ITEMS; i++)
{
lst.Add(i);
}
int count = 0;
sw.Reset();
//GC.Collect();
sw.Start();
for (int i = 0; i < NUM_LOOPS2; i++)
{
count = lst.Count;
}
sw.Stop();
Console.Out.WriteLine("Took " + (sw.ElapsedMilliseconds) + "ms - 1.");
sw.Reset();
//GC.Collect();
sw.Start();
for (int i = 0; i < NUM_LOOPS2; i++)
{
count = ilst.Count;
}
sw.Stop();
Console.Out.WriteLine("Took " + (sw.ElapsedMilliseconds) + "ms - 2.");
}
}
Обратите внимание, что сбор мусора, похоже, не влияет на этот тест.