меня интересует производительность итераций .. что быстрее выполнить от начала до конца?
nkint
Ответы:
62
Из (датированного, но все еще очень полезного) резюме SGI STLdeque :
Двусторонняя очередь очень похожа на вектор: как и вектор, это последовательность, которая поддерживает произвольный доступ к элементам, постоянную вставку и удаление элементов в конце последовательности, а также линейную вставку и удаление элементов в середине.
Основное отличие deque от вектора заключается в том, что deque также поддерживает вставку и удаление элементов с постоянным временем в начале последовательности. Кроме того, deque не имеет каких-либо функций-членов, аналогичных функциям vector capacity () и reserve (), и не предоставляет никаких гарантий допустимости итератора, связанных с этими функциями-членами.
Список - это двусвязный список. То есть это Последовательность, которая поддерживает как прямой, так и обратный обход, а также (амортизированную) вставку и удаление элементов с постоянным временем в начале, в конце или в середине. Списки обладают важным свойством, заключающимся в том, что вставка и сращивание не делают недействительными итераторы для элементов списка, и даже удаление делает недействительными только итераторы, указывающие на удаляемые элементы. Порядок итераторов может быть изменен (то есть у list :: iterator может быть другой предшественник или преемник после операции со списком, чем раньше), но сами итераторы не будут аннулированы или сделаны так, чтобы указывать на другие элементы, если это недействительность или мутация явная.
Таким образом, контейнеры могут иметь общие процедуры, но гарантии времени для этих процедур различаются от контейнера к контейнеру . Это очень важно при рассмотрении того, какой из этих контейнеров использовать для задачи: учет того, как контейнер будет использоваться чаще всего (например, больше для поиска, чем для вставки / удаления), имеет большое значение для направления вас к нужному контейнеру. .
std :: list также имеет метод splice, который позволяет объединить два списка вместе
Рик,
25
Собственно, гарантии по времени - вторая по важности характеристика списка. Наиболее важной особенностью списка является то , что вы можете добавлять и удалять элементы , а не аннулирует ваши итераторы! В (почти?) Каждом другом контейнере STL каждая операция редактирования делает недействительными все ваши итераторы, поэтому для «удаления совпадающих элементов» вам нужно накапливать совпадающие элементы в одной операции, а затем удалять их в другой. В списке вы можете просмотреть его, удалить и добавить по своему усмотрению, и вам никогда не придется пересчитывать итератор.
Том Свирли
2
Это тоже абстрактные различия, так что измерьте реальность для своего случая! И list, и deque имеют O (1) вставку / удаление, но не забывайте, что это означает k * O (1), а k имеет разные значения для list и deque. В моем случае добавление объекта в список занимало в десять раз больше времени, чем двухсторонняя очередь, потому что для списка требовалось больше вызовов new / delete. Очевидно, это будет зависеть от того, какая у вас реализация STL.
Энди Кроувел
130
Позвольте мне перечислить различия:
Deque управляет своими элементами с помощью
динамического массива , обеспечивает произвольный доступ и имеет почти тот же интерфейс, что и вектор.
List управляет своими элементами как
двусвязным списком и не обеспечивает произвольный доступ .
Deque обеспечивает быструю вставку и удаление как в конце, так и в начале. Вставка и удаление элементов в середине происходит относительно медленно, потому что все элементы до любого из обоих концов могут быть перемещены, чтобы освободить место или заполнить пробел.
В List вставка и удаление элементов выполняется быстро в каждой позиции, включая оба конца.
Deque : любая вставка или удаление элементов, кроме начала или конца, делает недействительными все указатели, ссылки и итераторы, которые относятся к элементам deque.
Список : вставка и удаление элементов не делает недействительными указатели, ссылки и итераторы на другие элементы.
Сложность
Insert/erase at the beginningin middle at the end
Deque: Amortized constant Linear Amortized constantList: ConstantConstantConstant
@aJ: В чем разница между constantи amortized constant?
Lazer
17
Операции в долгосрочной перспективе ведут себя, как описано. Однако одна операция может занять больше времени, чем указано. Пример: вставить элемент в вектор, текущая емкость которого равна 10, а размер уже 9 является постоянным, где время линейно, если емкость равна 10, а размер также равен 10. Это потому, что он должен выделить и скопировать все элементы в новую память .
А.Дж.
5
@aJ: Как deque обеспечивает произвольный доступ? Также как реализована эта структура?
std::deque, с другой стороны, реализовано больше похоже std::vector. Он имеет постоянное время доступа по индексу, а также возможность вставки и удаления в начале и в конце, что обеспечивает кардинально отличные характеристики производительности от списка.
Еще одна важная гарантия - это способ хранения данных в памяти каждым контейнером:
Вектор - это один непрерывный блок памяти.
Двусторонняя очередь - это набор связанных блоков памяти, в каждом из которых хранится более одного элемента.
Список - это набор элементов, рассредоточенных в памяти, т. Е. В каждом «блоке» памяти хранится только один элемент.
Обратите внимание, что двухсторонняя очередь была разработана, чтобы попытаться сбалансировать преимущества как вектора, так и списка без их соответствующих недостатков. Это особенно интересный контейнер для платформ с ограничением памяти, например микроконтроллеров.
Стратегия хранения в памяти часто упускается из виду, однако часто это одна из наиболее важных причин для выбора наиболее подходящего контейнера для определенного приложения.
Нет. Двухсторонняя очередь поддерживает только вставку и удаление O (1) спереди и сзади. Это может быть, например, реализовано в векторе с циклическим переходом. Поскольку он также гарантирует произвольный доступ O (1), вы можете быть уверены, что он не использует (просто) двусвязный список.
Разницу в производительности хорошо объяснили другие. Я просто хотел добавить, что подобные или даже идентичные интерфейсы распространены в объектно-ориентированном программировании - это часть общей методологии написания объектно-ориентированного программного обеспечения. НИКОГДА не следует предполагать, что два класса работают одинаково просто потому, что они реализуют один и тот же интерфейс, равно как и вы не должны предполагать, что лошадь работает как собака, потому что оба они реализуют attack () и make_noise ().
Вот доказательство концепции использования кода списка, неупорядоченной карты, которая дает O (1) поиск и O (1) точное обслуживание LRU. Требуются (не стертые) итераторы, чтобы выдержать операции стирания. Планируйте использовать в O (1) произвольно большой программно управляемый кеш для указателей ЦП в памяти графического процессора. Указывает на планировщик Linux O (1) (LRU <-> очередь выполнения на процессор). Unordered_map имеет постоянный доступ по времени через хеш-таблицу.
#include<iostream> #include<list> #include<unordered_map> usingnamespacestd;
structMapEntry {list<uint64_t>::iterator LRU_entry;
uint64_t CpuPtr;
};
typedefunordered_map<uint64_t,MapEntry> Table;
typedeflist<uint64_t> FIFO;
FIFO LRU; // LRU list at a given priority
Table DeviceBuffer; // Table of device buffersvoidPrint(void){
for (FIFO::iterator l = LRU.begin(); l != LRU.end(); l++) {
std::cout<< "LRU entry "<< *l << " : " ;
std::cout<< "Buffer entry "<< DeviceBuffer[*l].CpuPtr <<endl;
}
}
intmain(){
LRU.push_back(0);
LRU.push_back(1);
LRU.push_back(2);
LRU.push_back(3);
LRU.push_back(4);
for (FIFO::iterator i = LRU.begin(); i != LRU.end(); i++) {
MapEntry ME = { i, *i};
DeviceBuffer[*i] = ME;
}
std::cout<< "************ Initial set of CpuPtrs" <<endl;
Print();
{
// Suppose evict an entry - find it via "key - memory address uin64_t" and remove from // cache "tag" table AND LRU list with O(1) operationsuint64_t key=2;
LRU.erase(DeviceBuffer[2].LRU_entry);
DeviceBuffer.erase(2);
}
std::cout<< "************ Remove item 2 " <<endl;
Print();
{
// Insert a new allocation in both tag table, and LRU ordering wiith O(1) operationsuint64_t key=9;
LRU.push_front(key);
MapEntry ME = { LRU.begin(), key };
DeviceBuffer[key]=ME;
}
std::cout<< "************ Add item 9 " <<endl;
Print();
std::cout << "Victim "<<LRU.back()<<endl;
}
Ответы:
Из (датированного, но все еще очень полезного) резюме SGI STL
deque
:Вот сводка
list
с того же сайта:Таким образом, контейнеры могут иметь общие процедуры, но гарантии времени для этих процедур различаются от контейнера к контейнеру . Это очень важно при рассмотрении того, какой из этих контейнеров использовать для задачи: учет того, как контейнер будет использоваться чаще всего (например, больше для поиска, чем для вставки / удаления), имеет большое значение для направления вас к нужному контейнеру. .
источник
Позвольте мне перечислить различия:
Сложность
Insert/erase at the beginning in middle at the end Deque: Amortized constant Linear Amortized constant List: Constant Constant Constant
источник
constant
иamortized constant
?std::list
это в основном двусвязный список.std::deque
, с другой стороны, реализовано больше похожеstd::vector
. Он имеет постоянное время доступа по индексу, а также возможность вставки и удаления в начале и в конце, что обеспечивает кардинально отличные характеристики производительности от списка.источник
Еще одна важная гарантия - это способ хранения данных в памяти каждым контейнером:
Обратите внимание, что двухсторонняя очередь была разработана, чтобы попытаться сбалансировать преимущества как вектора, так и списка без их соответствующих недостатков. Это особенно интересный контейнер для платформ с ограничением памяти, например микроконтроллеров.
Стратегия хранения в памяти часто упускается из виду, однако часто это одна из наиболее важных причин для выбора наиболее подходящего контейнера для определенного приложения.
источник
Нет. Двухсторонняя очередь поддерживает только вставку и удаление O (1) спереди и сзади. Это может быть, например, реализовано в векторе с циклическим переходом. Поскольку он также гарантирует произвольный доступ O (1), вы можете быть уверены, что он не использует (просто) двусвязный список.
источник
Разницу в производительности хорошо объяснили другие. Я просто хотел добавить, что подобные или даже идентичные интерфейсы распространены в объектно-ориентированном программировании - это часть общей методологии написания объектно-ориентированного программного обеспечения. НИКОГДА не следует предполагать, что два класса работают одинаково просто потому, что они реализуют один и тот же интерфейс, равно как и вы не должны предполагать, что лошадь работает как собака, потому что оба они реализуют attack () и make_noise ().
источник
Вот доказательство концепции использования кода списка, неупорядоченной карты, которая дает O (1) поиск и O (1) точное обслуживание LRU. Требуются (не стертые) итераторы, чтобы выдержать операции стирания. Планируйте использовать в O (1) произвольно большой программно управляемый кеш для указателей ЦП в памяти графического процессора. Указывает на планировщик Linux O (1) (LRU <-> очередь выполнения на процессор). Unordered_map имеет постоянный доступ по времени через хеш-таблицу.
#include <iostream> #include <list> #include <unordered_map> using namespace std; struct MapEntry { list<uint64_t>::iterator LRU_entry; uint64_t CpuPtr; }; typedef unordered_map<uint64_t,MapEntry> Table; typedef list<uint64_t> FIFO; FIFO LRU; // LRU list at a given priority Table DeviceBuffer; // Table of device buffers void Print(void){ for (FIFO::iterator l = LRU.begin(); l != LRU.end(); l++) { std::cout<< "LRU entry "<< *l << " : " ; std::cout<< "Buffer entry "<< DeviceBuffer[*l].CpuPtr <<endl; } } int main() { LRU.push_back(0); LRU.push_back(1); LRU.push_back(2); LRU.push_back(3); LRU.push_back(4); for (FIFO::iterator i = LRU.begin(); i != LRU.end(); i++) { MapEntry ME = { i, *i}; DeviceBuffer[*i] = ME; } std::cout<< "************ Initial set of CpuPtrs" <<endl; Print(); { // Suppose evict an entry - find it via "key - memory address uin64_t" and remove from // cache "tag" table AND LRU list with O(1) operations uint64_t key=2; LRU.erase(DeviceBuffer[2].LRU_entry); DeviceBuffer.erase(2); } std::cout<< "************ Remove item 2 " <<endl; Print(); { // Insert a new allocation in both tag table, and LRU ordering wiith O(1) operations uint64_t key=9; LRU.push_front(key); MapEntry ME = { LRU.begin(), key }; DeviceBuffer[key]=ME; } std::cout<< "************ Add item 9 " <<endl; Print(); std::cout << "Victim "<<LRU.back()<<endl; }
источник
Среди выдающихся различий между
deque
иlist
Для
deque
:Предметы хранятся рядом;
Оптимизирован для добавления данных с двух сторон (спереди, сзади);
Элементы индексируются числами (целыми числами).
Может просматриваться итераторами и даже индексом элемента.
Время доступа к данным быстрее.
За
list
Элементы хранятся в памяти «случайным образом»;
Доступен для просмотра только итераторами;
Оптимизирован для вставки и извлечения посередине.
Доступ по времени к данным медленнее, медленнее для итерации из-за очень плохой пространственной локализации.
Очень хорошо справляется с крупными элементами
Вы также можете проверить следующую ссылку , в которой сравнивается производительность между двумя контейнерами STL (с std :: vector)
Надеюсь, я поделился полезной информацией.
источник