Pull to refresh

Comments 48

Для вашего проекта 1% производительности и 1 мб сэкономленной памяти стоили написания этого кода?
Вопрос надо пересчитывать в деньги. Затрудняюсь сказать, как это делать для игровой индустрии (насколько я понял, автор поста чем-то вроде занимается).

А для серверного ПО это полегче сделать. Скажем, оптимизация даст прирост 1% производительности. Один сервер обслуживает 1000 клиентов, теперь сможет обслужить 1010. Каждый клиент дает 10 тугриков в месяц. Если реализация нового кода займет день при з/п программиста 2000 тугриков, то уже через месяц она себя окупит.

Так же легко пересчитать, например, затраты на трафик.

Вот только пока оптимизацию не сделаешь (не запрограммируешь), реальный прирост производительности не узнаешь. С этой стороны бида и пичаль :)
Оптимизировать-то можно много что. Как-то даже не верится, что самым узким местом системы оказался std::vector.
И мне не верится. Автор, показывай результаты профайлера!
Согласен, скорее узким местом станет неправильный выбор контейнера.
Как-то вы не верно считаете, есть 10 серверов, все забиты под завязку и обслуживают 1000 клиентов, приходит ещё 10, что дешевле купить ещё один сервер или сделать оптимизацию, а если ещё учесть что чуть позже придет ещё 10 клиентов и сервер все-таки покупать придется. Мне кажется надо ставить вопрос так, стоит ли труд программиста и увелечение сложности поддержки кода цены экономии на железе, которое компенсирует отстутвие оптимизации. Конечно это не универсальный вопрос на все случаи, бывают и исключения.
Скажем, оптимизация даст прирост 1% производительности… каждый клиент дает 10 тугриков в месяц. Если реализация нового кода займет день при з/п программиста 2000 тугриков, то уже через месяц она себя окупит.

1% — это всё равно 1%, даже если в абсолютных цифрах это $1M. Для того, для кого этот лям является одним процентов, это всё равно не деньги. Как и для кодера на 30k лишние 300 рублей в месяц — не деньги. Вкладывая душу в то, что принесёт лишний 1%, вы занимайтесь фигнёй ;) просто программеры любят заниматься тем, что интересно вместо того, чтобы заниматься тем, что важно.
> Для того, для кого этот лям является одним процентов, это всё равно не деньги.

Потрясающее знание психологии миллионеров.
Скорее знание психологии нищебродов, которые никак не могут воткнуть, что пока они придают значение 100-рублёвым купюрам, так они всю свою жизнь в ста рублях и просидят. «Прикинь Вася, Ламбарджыни Диабла стоит триста тыщ долларов! вот у нас, к примеру, пакет гречки стоит 20 рублей.»
Вот именно. «Процент — не деньги» — это психология нищебродов.
Скорее как раз «процент — деньги», только отношение к этим деньгам у них какое-то странное :) сначала они переводят процент в деньги от оборота конторы, а затем меряют получившуюся сумму на свой карман — в итоге охреневают и стимулируют собственное ИБД, совершенно забывая о долях. С другой стороны, тут же вопят о том как билайн развёл их на 30 рублей услугой гудок при том что эти 30 рублей даже процента месячного дохода для них не составляют. Видимо дело в привычке уделять чужому карману больше внимания, чем своему собственному.
«30 рублей даже процента месячного дохода для них не составляют», т.е. у них доход больше 3000 рублей? плохое сравнение (:
Так о том и речь — когда 1% даёт миллион на выходе в чужом кошельке, то это «п-ц как круто». Когда же 30 рублей составляют меньше 1 промилле своего собственно — «ист обирайтунг! грааабэн».
Если у вас сложная система моделирования гидродинамики или молекулярной динамики, которая потребляет 500Гб оперативной памяти (заполняя небольшие массивы), и расчет производит за 24 часа на суперкомпьютере, притом доступ к этим массивам происходит постоянно, то это весьма полезный код.
Кто же будет делать такую систему на куче мелких объектов с динамическими массивами внутри
Для большей ясности я бы просил описать более или менее точно ситуацию разработки. Судя по описанным показателям повышения производительности, использование такой реализации контейнера имеет смысл только в том случае, когда в памяти динамически создается и удаляется множество буферов, наполняемость которых априори не известна. Возможно, такая ситуация и возникает в некоторых нагруженных мультимедиа-приложениях, но и в таком случае ее можно было бы достаточно легко обойти. Хотелось бы увидеть реальное применение приведенного кода с достаточно точной оценкой прироста производительности.
UFO just landed and posted this here
Ну, если одна линия — 32 байта, и у вас уже есть объект размером 28 байт, то да — добавление лишних 8 байт к объекту вызовет необходимость использовать лишнюю линию кэша под каждый объект. А их может быть много.
Об этом и речь.
UFO just landed and posted this here
И все же создается впечатление, что это в чистом виде just for fun. Уловить разницу в 1% довольно трудно, а время потрачено.
Даже если так — что в этом плохого?
Ничего плохо в этом, конечно, нет. Я и сам, порой, люблю велосипеды поконструировать :-) Просто высказал мнение о статье, критиковать я никого не хотел.
Исключительной целью такой статьи мог бы быть лишь спортивный интерес. И ничего больше.
Нестандартная реализация вектора — зло. Ловля блох приводит к незначительным, я бы сказал, эфемерным выигрышам и огромной платой за надежность такого кода.
STL тестировалась миллионами проектов.
STL создавалось для «сферического проекта», усреднённая библиотека, угодная всем, а вот написание своих контейнеров для некоторых это насущная задача. Всё-таки десктопом разработка ПО не ограничивается и не нужно делать таких громких (и местами глупых) выводов. Да и на десктопе бывают разные, пусть и незначительные, ситуации.
Не стоит переходить на личности. Спор из технической плоскости переходит в разряд — дурак, сам дурак.
Хабр стал помойкой, где завсегдатаи хамят любым незнакомым людям.
Не стоит писать «среднестатические» глупости с таким апломбом. Сам Степанов, Мейерс и другие умные люди говорили, что не видят ничего плохого в отдельных реализациях контейнеров, если они подкреплены необходимостью и целью. А такие вот заученные глупости типа «что-то там — зло» и «кто-то там тестировал что-то там миллион раз» показывают всего лишь ограниченное мышление, зашоренное глупыми штампами. Понимать нужно, а не повторять за другими. Собственно, если условия и цели показывают, что стандартная реализация не подходит и выигрыш очевиден (пусть даже этот 1%, который, например, в рамках embedded либо gamedev очень много значит), то переписать стоит и побыстрее, не оглядываясь на «штампы».
Уж лучше STLPort (который по ряду бенчмарков рвет STL даже с рядом выключенных опций). Или свое решение, если оно действительно оптимально. Есть множество desktop приложений, для которых каждая мс — на вес золота.

Надеюсь, за MFC заступаться никто не будет?
Каждый програмист должен собрать свой велосипед, написать свою реализацию std::vector и, конечно же, определить менеджер памяти или другой SmallObjectAllocator.
Это не стеб — лично я всегда начинаю новый проект подключением своего вектора, nedAlloca и Winnie аллокатора.
Разобраться как и почему работает в std или в mfc — очень полезная практика.
Только занимать этим надо не на работе, а до нее( идельно еще в школе )
Мне интересно, а других мест в проекте, которые можно было бы оптимизировать, уже нет? Почему взялись за написание своего vector'а (и получили в результате этот 1%), а не за что-нибудь другое?
Нужно сказать, что желание иметь более компактный массив возникла ещё в конце 90-х, когда памяти было совсем не так много, как сейчас, и целью была именно оптимизация по памяти. Время шло, объём памяти увеличивался, но тем не менее оптимизация по памяти (теперь уже по кэш-памяти) всё ещё актуальна. Описанную реализацию я использую довольно давно (с 2006 года), и для того чтобы привести реальные цифры в статье для сравнения пересобрал проект со стандартным контейнером.
Так что это не было какой-то специальной оптимизацией последнего времени.
Тем не менее сейчас проект находится в таком состоянии, что профайлер не показывает пиков больше 5% на функцию, а самая «тяжёлая» подсистема (набор функций) занимает порядка 20%. И одним из узких мест оказывается память. Поэтому приходится думать и о втискивании структур в кэш-строки, и об их выравнивании.
В общем оптимизация в 1% это совсем неплохо, десяток таких оптимизаций и вот вам дополнительные 5 FPS.
Насколько я понял у вас из 270 000 массивов 170 000 пустых. Может лучше не оптимизировать vector, а не держать в памяти 170000 пустых массивов?
Давайте рассмотрим на примере. Есть модель (дерево, лавочка, машинка), требуется уметь повреждать эту модель — рисовать на ней какие-то следы повреждений. Хранить информацию о повреждениях удобно в массиве. В начале игры все модели как новенькие, без всяких повреждений и все эти массивы пусты.
Альтернатива — хранить повреждения в ассоциативном контейнере, и при отрисовке модели искать соответствующие ей повреждения, но этот поиск потребует значительно больше вычислительных затрат, чем просто пройтись по массиву.
То есть альтернатива есть, но она тоже имеет цену, и эта цена выше 4-х байт в структуре модели.
В чем проблема создавать массив при первом повреждении?
Но ведь всё равно в структуре модели нужно иметь поле данных, в который запишется указатель на этот созданный массив. Cобственно, предложенная в статье реализация по этому принципу и работает.
перечитал топик еще раз. вдумался. врубился.
очень прикольно. пишите еще.
Не смог найти в заголовках EASTL реализации с такими же возможностями.
Структура, от которой наследуется релизованный там vector:
template <typename T, typename Allocator>
struct VectorBase
{
     T* mpBegin;
     T* mpEnd;
     T* mpCapacity;
     allocator_type mAllocator; // To do: Use base class optimization to make this go away.
}
Особенно понравилось To do.
Ну надо же, целый мегабайт сэкономили :7
А почему у вас в проекте так много динамических массивов? Я не представляю себе игру с такими потребностями. Или у вас что-то посерьёзнее? В любом случае, мне кажется, ~270000 vector'ов — это подозрительно много.
Обычная 3D-гонка, а-ля NFS. В игровом мире ~15000 объёмов, ~5000 расставленных моделей и т.д., в общем набирается.
Подписываюсь. В игровом проекте ~300k контейнеров и «производительность» — несочетаемые вещи. Это уже проблема архитектуры, которую пытаются решать напильником на низком уровне. Первое решение, которое приходит в голову — просто хранить эти свойста по всем сущностям в мире в одном месте, а не размазывать по памяти равномерно. Эффект будет приятней, как по объему занимаемой памяти, так и по фрагментации.
Если хотите заняться оптимизацией конкретного проекта, нужно использовать профайлер.
А если хочется оптимизации инструментов, то в частности для STL существует очень интересная проблема фрагментации памяти, которая, я уверен, скушает больше 1%.
А чтобы уменьшить эту самую фрагментацию и еще оптимизировать по скорости сам процесс выделения памяти лучший выход для геймдева — вообще не перераспределять память динамически =) Либо писать свой менеджер памяти.
Профайлер, конечно, штука необходимая. Но код шаблонов вроде vector «размазан» по сотням функций, и в каждой занимает доли процента, на профайлере просто ничего не увидишь. На нашем проекте самая «тяжёлая» функция занимает 5% времени, а 70% времени занимают функции не превосходящие 1%. Это только в книжках красиво пришут про вред ранней оптимизации, мол запустите профайлер, увидите пики, их и оптимизируйте. А что делать, когда пиков нет?
Касательно динамического распределение памяти (есть и свой менеджер памяти, и не один, куда ж без них), бОльшая часть памяти распределяется на этапе загрузки игры, в процессе игры работа с памятью сведена к минимуму, но раз уж в игре происходят динамические явления (повреждения моделей, отрыв частей и т.п.), информацию о них нужно где-то хранить.
Да, согласен, практика, как обычно, заставляет искать нестандартные решения. Я в сложных ситуациях использую практику точечного профайлера — делаю необходимые замеры только в точках, которые подозреваю на неоптимальность, ищу такие области путем расстановки «ворот» профайлера от общего к частному. Использую для этого самописный простенький профайлер, который допиливаю по мере необходимости. «Большими» профайлерами пользуюсь на самых ранних этапах.
По поводу распределения памяти — значит все под контролем, супер =)
Что все накинулись на автора с профайлером? Идея красивая сама по себе.
Вы бы ещё про целесообразность perl golf поспорили.
Буквально на днях столкнулся с тем, что std::vector «тяжелый». В моем случае мне надо было создавать огромное количество объектов, которые имели вложенные векторы. И лишние 12 байт на вектор выходили узким местом, выедая память на хранение пустоты.
Большое спасибо за код!
Иногда, кстати, делают и наоборот, особенно в std::string: заводят в контейнере небольшой буфер, чтобы для маленького количества элементов не вызывать медленные new и delete.
Производительность немного повысилась, примерно на 1%? Ну, и насколько практичной можно считать облегчённую реализацию вектора со столь малым приростом производительности?
Sign up to leave a comment.

Articles