Приветствую. Недавно столкнулся вот с такой трудностью: писал задачу на обход в ширину, и потребовалось использовать очередь . Я воспользовался STL, и мне показалось, что из-за этого программа выполнялась существенно дольше, чем в случае использования массива, как предлагалось поступить в решении.
Вопрос: бывают ли случаи, когда использовать библиотеку STL не имеет смысла, так как с ней программа выполняется дольше и не проходит по времени?
П.С. Да, я новичок, и, возможно, не знаю совершенно очевидных кому-то вещей. Вопрос гуглил, но ответа не нашел.
П.П.С. Посоветуйте книги типа этой, желательно, с примерами на С++.
Спасибо за внимание.
От stl надо избавляться, если чувствуется, что программа по времени будет работать близко к пределу. Но обычно проблем не возникает(особенно когда сервер быстрый(например, здесь, на кодефорсес)).
При этом тормозят в основном vector и deque. А вот set и map обычно не сильно уступают(а иногда и выигрывают) у написанных своими руками сбалансированных деревьев.
В ace.delos.com советуют избегать от vector как от чумы.
Ну если использование вектора заметно упрощает жизнь, и при этом TL стоит с запасом, то избавляться от него не стоит(чтобы избежать лишних багов).
Дело не только в багах но и том, что писать свои дин.массивы сравнительно долго.
Это от лени объяснять, как в разных случаях ими грамотно пользоваться и избегать проблем со скоростью.
Да, но все же зачастую, задачу можно решить без их использования. Заюзав, например, сортировку, которая между прочим отличная, плюс бинпоиск или хэшмапу.
Хэшмапа вроде как очень тормознутая, ее лучше руками писать.
Естесственно руками, но в среднем стлвская хэшмапа у меня работала быстрее, но не сильно.
Это потому что ты писать хеш таблицы не умеешь. Stl-евская реализация тормозная. Скорость её можно существенно улучшить, если задать другую величину средней заполнености, но и в таком виде она остаётся тормозной.
Может, я что-то не понял. Но причем здесь мое умение писать хэштейбл? И вообще откуда такая инфа интересно?
UPD: я говорил о map и unordered_map, т.е. я делал исключительно свое сравнение между этими структурами из стандартной библиотеки. О своих хэшмапах я ничего не упоминал.
kraskevich:"Хэшмапа вроде как очень тормознутая, ее лучше руками писать."
notime.sea:"Естесственно руками, но в среднем стлвская хэшмапа у меня работала быстрее, но не сильно."
...
notime.sea:"О своих хэшмапах я ничего не упоминал."
no comments
"И вообще откуда такая инфа интересно?"
какая инфа? попытаюсь угадать ваш вопрос, предположим он должен был быть "откуда информация о скорости работы Stlевской хешмапы?" — из личного опыта, давным давно я для себя потестировал эту структуру, дабы оценить её полезность для acm.
Здесь и выше я говорю об stdext::hash_map ибо unordered_map есть только в C++11, а мы говорим об СП, где его использование не столь широко, и вообще я только сегодня узнал что есть такой класс. Не тестировал этот класс, но ставлю на то что рукописная реализация его также уделает. Дело в том что этот класс применим к более широкому классу задач чем наша самопальная хешмапа, которой не надо соответствовать каким-то правилам типа правильного удаления по константной ссылке на ключ. Нашей самопальной мапе не надо ресайзиться при заполнении, память под все элементы мы выделяем статически. В таких условиях самопальное всегда будет быстрее по определению.
Если я правильно понял, то от постоянного ресайза можно избавиться: http://cplusplus.com/reference/stl/unordered_set/reserve/
Да, можно зарезервировать нужное число бакетов, этим ты избавишься от ресайзов, но не от кода который это контроллирует. Я привёл несколько ограничивающих факторов не просто так. Круг задач этой сруктуры намного шире, и понятно что узкоспециализированное решение будет не медленнее, а на практике значительно быстрее.
Люблю подобные заявления, можете показать как и что вы измеряли?
Я вот недавно сравнивал в GCC4.6 unordered_map и map на 10 миллионах случайных строк длины 10 и получилось, что unordered_map в 5-10 раз быстрее, как при заполнении таблицы, так и при чтении.
вот и я об этом)
Насколько я знаю, всё как раз наоборот. vector в STL работает практически с такой же скоростью что и обычный массив. Вообще если вы заранее объявите размер вектора, то уступит он массиву может 5% времени. А вот set и map. написанные умелыми руками, не только более подойдут для специализированных задач, но будут работать быстрее. Однако писать свой set и map долго и почти всегда не нужно. Совсем другое дело string. Более ужасной вещи чем string представить трудно. Он может есть памяти не в 2, а чуть ли не в 5 раз больше чем использует на самом деле. При этом работает он ОЧЕНЬ медленно. Вот это точно чума.
Ну если размер вектора известен заранее, то он в этом случае вообще не нужен. А если много push_back'ть, то получается раза в 1,5 — 2 медленнее, чем массив.
вектор работает дольше массива — 100%, не знаю на сколько, то точно дольше
вы писали set быстрее стлевского? с map согласен, он ужасен, но вот обогнать set не могу уже давно...
У меня на рандомных тестах декартово дерево обгоняет set где-то на 20-30%.
поделитесь
Я использую массив крайне редко. В основном пользуюсь вектором. В это плане очень помогают конструкторы и функции
assign
иresize
. Например: нужно считать массив из n элементов. Очень часто вижу такое:, что есть сильное замедление программы и памяти в худшем случае может уйти O(2*n). Я предпочитаю так:
если же нужен вектор глобальный, то так: объявляю вектор за мэйном, а мэйне так:
Результат: я ни разу не получал ТЛ, связанный с вектором.
Вот как раз у меня есть вопрос на эту тему. Работает ли медленнее resize, чем передача размера в конструктор(в вашем первом варианте)? И еще один вопрос, уже по поводу assign. Какова скорость в сравнении с fill(iterator begin, iterator end, x)? Быстрее/Медленнее?
P.S. Можно делать так: a.resize(n, x) (Создает вектор из n элементов и заполняет числом/строкой/etc x)
Про второе значение в
resize
я не знал. А вот по поводу скоростиresize
и конструктора — не измерял, но когда заменял конструктор наresize
разницы не чувствовал. А проfill(iterator begin, iterator end, x)
я не слышал. Как я понял — это заполнение отbegin()
доend()
иксами?Да. Это в
<algorithm>
. Вот примеры использования:если бы вы хоть раз написали codechef, попытались сдать какую-нибудь более менее серьезную задачу, вы бы его обязательно получили, и ваше представление о векторе стало бы менее радужно
P.S. в нормальных же условиях, эти функции не сильно замедляют, другое дело, зачем тогда использовать вектора, их казалось бы, резонно использовать, если не знаешь зараннее какой массив понадобится
Все таки ты знаешь только максимальный размер, который может понадобиться. А вектору довольно удобно указывать именно тот размер, который поступает на вход. Ну это мое субъективное мнение :)
ну да, в чем-то я с тобой согласен, но посути массива достаточно, если известно сколько максимальный размер
Да. В общем можно юзать вектора, а если не заходит, то можно попробовать заменить на массивы :)
спасибо, приму к сведению.
Во. Дельный совет, сам так делаю и проблем не знаю. Еще имеет смысл использовать reserve в тех случаях, когда размер массива точно не известен. Например, это подойдет для запихивания простых чисел на отрезке.
У меня есть классная книга про STL
Я бы не рекомендовал использовать std::list. Основная причина это динамическое выделение памяти под каждый узел списка, что добавляет тормозов в зависимости от аллокатора (системного имею в виду, а не STLевского). Воторой негативный момент в динамической памяти это что она выделяется блоками, например в 32-х битных виндах эти блоки обычно по 16 байт, и даже если мы вызываем new для выделения памяти под структуру в 1 байт, у нас все равно выделится 16 байт. Кроме того организовать связный список руками не такая уж проблема. Особенно скорость списка критична когда нам нужна быстрая структура для доступа, и мы вместо map пишем свою хеш-мапу, ествественно нельзя здесь использовать тормознутые списки, надо руками писать свои.
Ну и для гиков аргументы:
1) юзая свои списки можно в одной структуре реализовать несколько списков, например я так поступаю с бором для ахо-корасик. Все переходы (вершина, буква) хрянятся в общей хеш-таблице, узлы из одной хеш-ячейки связаны между собой как связный список по полю next (цепочка для разруливания коллизий) также те же самые узлы связаны между собой в список по полю brother (список детей некоторого другого узла)
2) память можно выделить статически, типа
TMyNode mem[MAX_NODES];
это даёт возможность убрать из структуры поле Id т.к. Id вычисляется по адресу:
int GetId(TMyNode* node) { return node - mem; }
Я бы так сильно не бросался нападками на std::list для этого нужно привести свою реализацию и я могу хорошо раскритиковать твой код где всё гладко, а где плохо. Знаешь почему память выделяется блоками это сделано специально для оптимизации ведь для этого и создавалась страничная адресация, да ладно что тут говорить вернёмся к списку. list двунаправленный список то есть он жрёт больше памяти чем однонаправленный slist-список, но он быстрее работает по удалению и вставки узла где у 2-ух связного оценка в O(1) по сравнению с slist O(n)... Наверное у тебя каша в голове для каких реализаций применять ту или иную структуру данных, только без обид. Связные списки хороши тем что используют память эффективно без лишней памяти по сравнению с разряжёнными массивами и хэш-таблицами, например для создания ассоциативных структур можно выбирать использовать хэш-таблицу или BST-деревья, AVL-деревья или RB-деревья, ну да можно и декартовое дерево, а для линейных данных vector, deque, slist, list, queue, stack. Я как разработчик 3D-игр давно убедился в эффективности библиотеки STL почти никогда меня stl-контейнеры не подводили, для начала попробуй написать свою реализацию list для работы в боевых условиях где кол-во выборок зашкаливает. На счёт статических массивов ведь не всегда точно известно сколько будет входных данных или думаешь зря в языке С/С++ строки заканчиваются \0 — null terminate. Конечно статический массив работает быстрее динамического так как динамический массив теряет время на реаллокацию и копирование. Приведу немного прикладной пример, например вы разрабатываете векторный редактор и вы как разработчик не можете точно знать сколько объектов будет создавать пользователь и что на ум приходит какая структура данных, конечно динамический массив или связной список, вот как раз тут статический массив не подходит. И последнее ведь библиотека STL создавалась не для одного типа машин, теперь подумайте на досуге.
P.S. Чем сложнее код, тем больше число ошибок.
Очень хочется верить в то што я не кармлю троля а указыаю уважаемому разработчику 3d-игр на ошибки в частности то что знаки припинания некто неотменял и текст без них читать очень сложно попробуй самостоятельно прочитать свой текст и осознать что ты имел виду может даже руский не твой радной язык но этого из профиля не понять
В случае олимпиад все входные данные, параметры компилятора, тестирующие машины и прочие условия чётко ограничены и скорость работы стоит на первом месте перед читаемостью, поддержкой и всем прочим. На олимпиаде мне всё равно, что бывают little-endian и big-endian машины; я пишу код, который использует порядок байт и по очевидным причинам не будет работать на PowerPC. Мне неважно, что моей программе могут дать тест из строки длиной 2·105 символов, когда ограничения в два раза меньше — на нормальной олимпиаде (а их подавляющее число) виновато будет жюри, тест уберут и выполнят перетестирование.
Но мне существенно мешают те 500 мс, которые моя программа теряет на выделении памяти блоками по 16 байт (а это сильно медленнее единовременного выделения массива на 16 МиБ) и двухкратное замедление из-за прыжков по памяти.
Мои слова "Я бы не рекомендовал использовать std::list.", следует понимать как "Я бы не рекомендовал использовать в олимпиадном программировании std::list". Выше yeputons описал особенности применения списков в олимпиадном программировании.
Даже на практике, часто бывает очень полезно дать MemoryPool списку или любой другой структуре данных использующей динамическое выделение памяти, это даст выигрыш в производительности, и не сильно усложнит код. Заявление про кашу в голове о структурах данных довольно резкое, и я бы не кидался такими заявлениями в адрес опытных олимпиадников. Многие школьники успешно занимающиеся олимпиадами бьют в этом отношении многих опытных бородатых разработчиков.
ну и приведу простую реализацию, раз просили.
ну смотря на сколько использовать стл, если в случае с очередью ты запихиваешь <10^5 элементов, грубо говоря, не думаю что разница будет принципиальная с рукописной очередью, если же довольно много элементов, то однозначно стл-структура будет дольше работать
Как именно Вы компилировали и как именно меряли время?
В частности, не сыграл ли тут какой-то факт наподобие того, что в Visual Studio большинство STL-ных структур в Debug Mode проводят огромное количество не нужных для получения результата (но очень полезных с точки зрения контроля правильности!) дополнительных проверок, так что время может оказываться бОльшим в десятки раз, а иногда даже асимптотически (n2 против n log n); в Release mode все эти проверки отключаются, и получается вполне себе терпимое время работы, отличающееся от вылизанной низкоуровневой реализации не более чем в 1,1--1,5 раза.
UPD И ещё, если речь идёт о сдаче именно на информатиксе — можете назвать конкретную задачу и конкретное время сдачи; тут немало людей, у которых достаточно прав на информатиксе, чтоб посмотреть, в чём дело. Если оно конечно ещё надо...
Я использую командную строку linux + блокнот (если Вы это имели в виду). Что касается времени, сначала я заметил, что одна и та же задача с использованием стека выполняется несколько дольше, чем в случае применения массива. Впрочем, здесь оба решения укладывались в отведенное время с огромным запасом. Сейчас я обнаружил, что задача не укладывается во время, даже когда максимальные тесты значительно меньше, чем в исходной задаче , хотя действовал я по алгоритму, описанному в разборе.
Вероятно, я значительно усложнил программу, и поэтому она работает в разы дольше. В ближайшее время напишу аналогичную прогу с рукописной очередью, и если разница в работе будет значительной, то попрошу посмотреть, в чем дело.
Благодарю всех отписавшихся за помощь.
Почитал код 32-27505 Дмитрий Талецкий 645. Lines 2012-08-31 15:20:12 GNU C++ Частичное решение 3 3 Подробнее
По крайней мере в этой программе претензии к STL мимо кассы, проблема в алгоритме: он может добавлять одну и ту же клеточку в очередь по много-много раз (очень примерно — миллиарды раз для поля 20*20).
Так что не надо было столь уверенно говорить о STL и надо было приводить свой код (например, через ideone.com) прямо в вопросе.
Если посмотреть как устроен queue и deque в STL-e, легко понять почему он медленней самописной очереди (под самописной очередью я понимаю зацикленный массив фиксированного размера).
Сразу пару замечаний: детали реализации могут отличаться в зависимости от компилятора, и они не оговорены стандартом. Причина почему сделано описанным ниже образом заключается в том, что deque должен быть динамическим, то есть никакого ограничения на количество элементов заранее нет, плюс требуется push/pop_back, push/pop_front и random_access за учетное O(1).
Так вот упрощенно stack, queue, deque устроены следющим образом -- это список корзин (назовем их a_1, ..., a_n). В каждой корзине лежит фиксированное количество элементов (B), которые ты хранишь в очереди. В любой момент времени, хранящиеся в deque данные -- это (a_i[start], ... a_i[B-1], a_{i+1}[0], ..., a_j[0], ..., a_j[end]). Как устроены операции более менее понятно. Единственный нетривиальный случай -- когда заполняются первая или последняя корзина, в таком случае выделают еще корзину и добавляют её в массив корзин. Сами корзины (то есть указатели на корзины) скорее всего лежат в веторе, или двустороннем векторе. Прочитать подробнее можно в исходниках stl: c++_include/bits/stl_deque.
Такая структура работает достаточно быстро и удовлетворяет всех указанным требованиям, плюс съедает небольшое количество дополнительной памяти. Но надо понимать, что доступ к произвольному элементу требует (1) операции вычисления адреса этого элемента и (2) приводит к большему количеству cache miss-ов, так как данные лежат по бакетам, которые в памяти могут находится далеко друг от друга.
Теперь про vector vs статический массив. С алгоритмической точки зрения заранее выделенный vector нужного размера не должен ничем уступать стат-массиву. Однако в практике олимпиадного программирования такое возможно. Причина, как мне кажется, в том, что все стат. массивы обычно заводятся на стеке (глобальные переменные), что безусловно дает некоторое ускорение.
Статические объекты (со static storage duration) выделяются не на стеке, а в специально отведённой области памяти, которая создаётся при запуске программы.
Я проводил некоторые исследования по теме
vector
vs массив. Могу подтвердить, что в GCC операции доступа к элементам одинаковы в обоих случаях. Единственное различие — это необходимость одной лишней инструкции в случаеvector
, которая загружает адрес массива, хранящийся в объектеvector
. Естественно, в случае цикла по вектору эту инструкцию не нужно повторять при каждой итерации, так что она почти не влияет на скорость выполнения.deque да, а stack и queue — это адапторы. Не нравится deque? Передавай им другой контейнер.
По поводу разницы в скорости между вектором и статическим массивом.. Понятно, что вектор — это больше, чем статический массив, поэтому он в совокупности медленнее. По сравнению со статическим массивом вектор может:
Когда же мы работаем с вектором, как со статическим массивом, то есть итерация/обращение по индексу, то наличие разницы по скорости будет зависеть от конкретной реализации stl и конкретного компилятора. Я видел очень много обсуждений (не связанных с олимпиадами), где рассматривались тормоза вектора по сравнению с массивом при итерациях с обращением по индексу. Большинства этих обсуждений не было бы, если бы во многих версиях Dinkum STL (это именно та STL, что идет вместе с Visual Studio) в релизной версии vector::operator[] не было бы проверки на выход за пределы вектора (!). А она там была, поэтому обращение по индексу к вектору даже в релизе было совсем не таким же легким, как обращение по индексу к массиву. И поэтому сравнивать их было бесполезно в таком виде, сравнивать нужно было итерацию по вектору с помощью итераторов.
Так что в идеальном мире, где реализация вектора не содержит лишних телодвижений, где компилятор всегда сможет заинлайнить вызов operator[], скорость работы с вектором без изменения его размера не должна отличаться от скорости работы с массивом. В реальном мире может оказаться, что сравниться по скорости со статическим массивом может разве что итерация по вектору с помощью итератора.