Как я могу сделать свою функцию выполненной с такой скоростью, как “Содержит” на ArrayList?

Я не могу выяснить несоответствие между временем, которое требуется для Contains метод для нахождения элемента в ArrayList и время, которое требуется для небольшой функции, которую я записал, чтобы сделать то же самое. Документация указывает это Contains выполняет линейный поиск, таким образом, он, как предполагается, находится в O(n) и не любой другой более быстрый метод. Однако, в то время как точные значения не могут быть релевантными, Contains метод возвращается в 00:00:00.1087087 секунды, в то время как моя функция берет 00:00:00.1876165. Это не могло бы быть очень, но это различие становится более очевидным при контакте с еще большими массивами. Что является мной пропавшие без вести и как должен я писать свою функцию для соответствия Containsдействия?

Я использую C# на.NET 3.5.

public partial class Window1 : Window
{
    public bool DoesContain(ArrayList list, object element)
    {
        for (int i = 0; i < list.Count; i++)
            if (list[i].Equals(element)) return true;

        return false;
    }

    public Window1()
    {
        InitializeComponent();

        ArrayList list = new ArrayList();
        for (int i = 0; i < 10000000; i++) list.Add("zzz " + i);

        Stopwatch sw = new Stopwatch();
        sw.Start();

        //Console.Out.WriteLine(list.Contains("zzz 9000000") + " " + sw.Elapsed);
        Console.Out.WriteLine(DoesContain(list, "zzz 9000000") + " " + sw.Elapsed);
    }
}

Править:

Хорошо, теперь, парни, посмотрите:

public partial class Window1 : Window
{
    public bool DoesContain(ArrayList list, object element)
    {
        int count = list.Count;
        for (int i = count - 1; i >= 0; i--)
            if (element.Equals(list[i])) return true;

        return false;
    }


    public bool DoesContain1(ArrayList list, object element)
    {
        int count = list.Count;
        for (int i = 0; i < count; i++)
            if (element.Equals(list[i])) return true;

        return false;
    }

    public Window1()
    {
        InitializeComponent();

        ArrayList list = new ArrayList();
        for (int i = 0; i < 10000000; i++) list.Add("zzz " + i);

        Stopwatch sw = new Stopwatch();
        long total = 0;
        int nr = 100;

        for (int i = 0; i < nr; i++)
        {
            sw.Reset();
            sw.Start();
            DoesContain(list,"zzz");
            total += sw.ElapsedMilliseconds;
        }
        Console.Out.WriteLine(total / nr);


        total = 0;
        for (int i = 0; i < nr; i++)
        {
            sw.Reset();
            sw.Start();
            DoesContain1(list, "zzz");
            total += sw.ElapsedMilliseconds;
        }
        Console.Out.WriteLine(total / nr);


        total = 0;
        for (int i = 0; i < nr; i++)
        {
            sw.Reset();
            sw.Start();
            list.Contains("zzz");
            total += sw.ElapsedMilliseconds;
        }
        Console.Out.WriteLine(total / nr);
    }
  }

Я сделал в среднем 100 раз для двух версий моей функции (вперед и обратный цикл) и для значения по умолчанию Contains функция. Времена, которые я имею, 136 и 133 миллисекунды для моих функций и удаленного победителя 87 для Contains версия. Хорошо теперь, если, прежде чем Вы могли утверждать, что данные были недостаточны и я основывал свои заключения на первом, изолировал выполненный, что Вы говорите об этом тесте? Не делает только в среднем Contains выполните лучше, но это достигает последовательно лучших результатов в каждом выполнении. Так, есть ли некоторый недостаток в здесь для сторонних функций, или что?

6
задан luvieere 25 February 2010 в 21:19
поделиться

11 ответов

Используя приведенный ниже код, я смог относительно стабильно (в пределах нескольких мс) получить следующие значения времени:
1: 190 мс DoesContainRev
2: 198 мс DoesContainRev1
3: 188 мс DoesContainFwd
4: 203 мс DoesContainFwd1
5: 199 мс Содержит

Здесь следует обратить внимание на несколько вещей.

  1. Это запускается с кодом, скомпилированным для выпуска, из командной строки. Многие люди совершают ошибку при тестировании кода внутри среды отладки Visual Studio, не говоря уже о том, что кто-то здесь делал, но о чем-то, о чем следует быть осторожным.

  2. Список list [i] .Equals (element) выглядит немного медленнее, чем element.Equals (list [i]) .

     с использованием System; 
    с использованием System.Diagnostics; 
    с использованием System.Collections; 
     
     
    пространство имен ArrayListBenchmark 
     {{ {1}} class Program 
     {
    static void Main (string [] args) 
     {
    Секундомер sw = new Stopwatch (); 
    const int arrayCount = 10000000; 
    ArrayList list = new ArrayList (arrayCount); 
    for (int i = 0; i  = 0; i -) 
    if (element.Equals (list [i])) return true; 
     
    return false ; 
    } 
    public static bool DoesContainFwd (список ArrayList, элемент объекта) 
     {
    int count = list.Count; 
    for (int i = 0; i  = 0; i -) 
    if (list [i] .Equals (element)) return true; 
     
    return false; 
    } 
    public static bool DoesContainFwd1 (список ArrayList, элемент объекта) 
     {
    int count = list.Count; 
    for (int i = 0; i 
1
ответ дан 8 December 2019 в 05:54
поделиться

Во-первых, вы не запускаете его много раз и не сравниваете средние значения.

Во-вторых, ваш метод не будет изменен до тех пор, пока он не будет запущен. Таким образом, время своевременной компиляции добавляется ко времени его выполнения.

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

11
ответ дан 8 December 2019 в 05:54
поделиться

С действительно оптимизатором не должно быть никакой разницы, потому что семантика кажется одинаковой. Однако существующий оптимизатор может оптимизировать вашу функцию не так хорошо, как оптимизированный жестко запрограммированный Contains . Некоторые моменты для оптимизации:

  1. сравнение со свойством каждый раз может быть медленнее, чем обратный отсчет, а сравнение с 0
  2. сам вызов функции имеет снижение производительности
  3. использование итераторов вместо явной индексации может быть быстрее ( цикл foreach вместо простого for )
1
ответ дан 8 December 2019 в 05:54
поделиться

Используйте SortedList , Dictionary или System. Collections.ObjectModel.KeyedCollection для быстрого доступа на основе ключа.

var list = new List<myObject>(); // Search is sequential
var dictionary = new Dictionary<myObject, myObject>(); // key based lookup, but no sequential lookup, Contains fast
var sortedList = new SortedList<myObject, myObject>(); // key based and sequential lookup, Contains fast

KeyedCollection также работает быстро и позволяет индексированный поиск, однако его необходимо унаследовать, поскольку он является абстрактным. Следовательно, вам нужна конкретная коллекция. Однако с помощью следующего вы можете создать общий KeyedCollection .

public class GenericKeyedCollection<TKey, TValue> : KeyedCollection<TKey, TValue> {
   public GenericKeyedCollection(Func<TValue, TKey> keyExtractor) {
      this.keyExtractor = keyExtractor;
   }

   private Func<TValue, TKey> keyExtractor;

   protected override TKey GetKeyForItem(TValue value) {
      return this.keyExtractor(value);
   }
}

Преимущество использования KeyedCollection заключается в том, что метод Add не требует указания ключа.

0
ответ дан 8 December 2019 в 05:54
поделиться

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

Единственное различие, которое я вижу, это то, что вызов list[i] делает проверку границ для i, тогда как метод Contains этого не делает.

2
ответ дан 8 December 2019 в 05:54
поделиться

Во-первых, если вы используете типы, которые знаете заранее, я бы посоветовал использовать дженерики. Итак, List вместо ArrayList. Под капотом ArrayList.Contains на самом деле делает немного больше, чем то, что вы делаете. Следующее получено от отражателя:

public virtual bool Contains(object item)
{
    if (item == null)
    {
        for (int j = 0; j < this._size; j++)
        {
            if (this._items[j] == null)
            {
                return true;
            }
        }
        return false;
    }
    for (int i = 0; i < this._size; i++)
    {
        if ((this._items[i] != null) && this._items[i].Equals(item))
        {
            return true;
        }
    }
    return false;
}

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

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

1
ответ дан 8 December 2019 в 05:54
поделиться

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

Чтобы увидеть это, измените свои прогоны на такой вид:

    sw.Reset();
    sw.Start();
    for (int i = 0; i < nr; i++)
    {          
        DoesContain(list,"zzz");            
    }
    total += sw.ElapsedMilliseconds;
    Console.WriteLine(total / nr);

Я просто передвинул несколько строк. Проблема JIT оказалась незначительной при таком количестве повторений.

1
ответ дан 8 December 2019 в 05:54
поделиться

Мое предположение заключается в том, что ArrayList написан на C++ и может использовать преимущества некоторых микрооптимизаций (примечание: это предположение).

Например, в C++ вы можете использовать арифметику указателей (в частности, инкрементирование указателя для итерации массива), чтобы быть быстрее, чем использование индекса.

0
ответ дан 8 December 2019 в 05:54
поделиться

Поскольку вы используете .NET 3.5, почему вы используете для начала ArrayList , а не List ?

Несколько вещей, которые стоит попробовать:

  • Вы могли видеть, помогает ли использование foreach вместо for цикла
  • Вы можете кэшировать счетчик:

     public bool DoesContain (список ArrayList, элемент объекта) {{ 1}} {
    int count = list.Count; 
    for (int i = 0; i 
  • Вы можете перевернуть сравнение:

     if (element.Equals (list [i])) 
     

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

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

5
ответ дан 8 December 2019 в 05:54
поделиться

используя структуру массива, вы не можете искать быстрее, чем O(n) без дополнительной информации. Если известно, что массив отсортирован, то можно использовать алгоритм бинарного поиска и потратить всего o(log(n)). В противном случае вы должны использовать набор.

0
ответ дан 8 December 2019 в 05:54
поделиться

Изменено после прочтения комментариев:

Он не использует некоторый хеш-алгоритм для обеспечения быстрого поиска.

0
ответ дан 8 December 2019 в 05:54
поделиться
Другие вопросы по тегам:

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