- 1
- 2
- 3
- 4
- 5
template<class Container>
void COW_guard(Container& forUnCow){
const Container c={};
cc+=c;
}
Нашли или выдавили из себя код, который нельзя назвать нормальным, на который без улыбки не взглянешь? Не торопитесь его удалять или рефакторить, — запостите его на говнокод.ру, посмеёмся вместе!
+17
template<class Container>
void COW_guard(Container& forUnCow){
const Container c={};
cc+=c;
}
LispGovno 06.05.2014 15:49 # 0
bormand 06.05.2014 16:15 # +3
bormand 06.05.2014 16:38 # +1
Но зачем? Там какой-то баг с implicit share нашелся?
LispGovno 06.05.2014 17:24 # +1
Но зачем? о_О
А пофиг. Я не из-за этого постил.
COW_guard используется чтобы форсировать копирование контейнера в Qt после копирования, тк пока не было изменений и копировать его никто не собирается.
Используется например в тех случаях когда используется std::unique_lock перед тем как разблокировать мьютекс, защищающий контейнер, в который недавно "скопировали" через COW данные из другого контенера. Чтобы внезапно в другом потоке не начались какие-нибудь копирования.
Ну или используется например чтобы отдавать STL-итератор на контейнер, не боясь что он внезапно станет не валидным, после того как внезапно начнутся COW копирования.
bormand 06.05.2014 17:54 # +1
Это вопрос производительности? Имхо, там вполне логично сделано - кто портит implicit shared контейнер со счетчиком больше 1, тот и платит за копирование его данных.
Ну и если implicit sharing реально мешает, и всегда хочется именно копирования, а не шаринга, то почему бы не юзать тупые и надежные stl контейнеры, чем городить эти костыли с рукопашным detach'ем?
bormand 06.05.2014 18:05 # +1
Если это неконстантный итератор - то детач произойдет сразу же, и счетчик станет равен одному. Главное отличие от STL - ни в коем случае нельзя самому делать копии того инстанса контейнера, у которого ты взял неконстантный итератор, пока этот итератор живы и используется. Т.к. тогда COW может случиться в момент записи через итератор: Если же это константный итератор - там вообще те же гарантии, что и в STL.
Если я в чем-то ошибаюсь - поправь меня.
bormand 06.05.2014 18:31 # +1
Тьфу ты. Наоборот же. COW не случится при записи через итератор, и пятерка попадет в расшаренный между v и v2 блок данных, что совсем не комильфо. Но суть это не меняет :)
bormand 06.05.2014 18:15 # +2
Сейча попробую найти пруфы, или же разубедить себя в этом ;)
bormand 06.05.2014 18:46 # +3
Так что все потокобезопасно, хоть и возможно лишнее копирование. Т.е. при одновременном детаче оба треда увидят в счетчике 2, каждый сделает себе по копии, затем один из них уменьшит счетчик до 1, а второй до нуля и ёбнет оригинальный блок данных.
LispGovno 06.05.2014 19:16 # +6
LispGovno 06.05.2014 19:46 # 0
roman-kashitsyn 06.05.2014 19:52 # +3
bormand 06.05.2014 19:57 # +1
Смотря как юзать... stl итераторы, если честно, ничуть не безопаснее обычных указателей...
А коровья защита (с точки зрения корректности проги) нужна только в одном случае - в момент, когда ты хочешь сделать снепшот контейнера при живых итераторах.
P.S. Жалко, что они не замутили метод clone(). Он бы решил эту проблему.
defecate-plusplus 06.05.2014 16:00 # +3
благодаря тебе узнал о такой занятной штуке как cattle grid
3.14159265 06.05.2014 20:31 # +2
Так fences в с++11 появились.
defecate-plusplus 06.05.2014 20:33 # +1
/green
3.14159265 06.05.2014 20:34 # +1
LispGovno 06.05.2014 20:59 # +1
3.14159265 06.05.2014 21:17 # +2
LispGovno 06.05.2014 21:21 # 0
3.14159265 06.05.2014 21:32 # +2
А вот вообще интересная оптимизация выпилить вообще COW, как делают lock elision (выпиливают лочки и заборы вообще, когда она ненужны). Ведь COW часто делает бессмысленное копирование.
То есть если компилер/среда видит что объект при COW-мутации не утекает и более не используется никем. Самая частая ситуация, ссылка на старый херится, и подхватывается новый.
Типичный пример применения на немутабельной строке:
for (int i=0;i<n;++i) s+="1";
Так вот в этом случае, когда escape analisys показывает что объект не форкается, а старая версия превращается в новую, никакого COW не происходит, а мутируется старый объект и отдается ссылка на него.
Пример: в метод заходит COW-список, и в цикле начинятеся.
Обычный расклад: делаем копию на каждом добавлении.
Предлагается: компилятор понимает что внутри метода COW не нужен и делает его 1 раз - при входе в цикл.
Я джва года жду такую оптимизацию.
bormand 06.05.2014 22:51 # +1
> никакого COW не происходит,
И правильно делает. Какое нахер COW на иммутабельной строке? COW оно только для мутабельных объектов, иначе какой в нем смысл.
3.14159265 06.05.2014 23:06 # 0
В этом его смысл. Эту строку мы не меняем, потому что на неё могут ссылаться другие. А создаём копию, однако видится как мутирование.
>COW оно только для мутабельных объектов
Нет. Поправьте если я ошибаюсь, но суть COW создавать еще одну немутабельную копию, при любом изменении. Никакой COW-объект не меняется (иммутабельный), меняется только ссылка.
bormand 06.05.2014 23:21 # +1
Если счетчик равен 1 (т.е. эксклюзивное владение) - еще как мутабелен.
Собственно почему оно и называется Copy On Write - сначала копируем ссылку на блок данных, в надежде на то, что никто не будет в него писать, а если все же кто-то собрался туда писать - делаем эксклюзивную копию.
3.14159265 06.05.2014 23:35 # +1
Я б не сказал что это говно, только с итераторами факап.
Но и в жабовских/гуавовских синхронизированных коллекциях тоже "забыли" их залочить, правда задокументировали в глубинах сырцов, но от этого не легче.
COW является надмножеством немутабельных объектов. Мы можем не городить схему со счетчиком ссылок, а тупо копировать на каждом write - оправдывая название. Это тоже кагбе COW. Читайте COW, в моих постах как создание иммутабельной копии.
bormand 06.05.2014 23:47 # +1
Множество мутабельных объектов тоже является надмножеством иммутабельных ;)
А implicitly shared объекты пытаются сделать вид, что они value type - т.е. мутабельные и передаваемые по значению. Если бы не та фигня с итератором - их бы можно было отличить от настоящей передачи по значению только по косвенным признакам (времени копирования и занимаемой памяти).
bormand 06.05.2014 23:33 # 0
3.14159265 06.05.2014 23:38 # +1
Примерно такая по духу техника ебашить спин-лок допустим 3 раза, если фейл - значит конкуренция и хватать лочку.
При отсутствии конкуренции будет дешевый спин-лок. При наличии спин-локи не будут тупить, а треды заснут до освобождения лочки.
bormand 06.05.2014 22:58 # +1
Ну в кутишных строках почти так. Настоящий detach() с копированием будет ровно 1 раз на первой итерации твоего цикла. Дальше в цикле будут только недорогие (по крайней мере для х86) atomic load'ы и периодическое растягивание буфера, когда результат перестанет в него влезать.
Вот если бы еще компилятор смог бы выпилить эти load'ы... Хотя, емнип, на х86 они стоят столько же, сколько и не атомарные, если их трогает только один тред (а внутри цикла их трогает как раз таки один тред). Или я туплю?
3.14159265 06.05.2014 23:32 # 0
Перечитал весь тред, пошёл прочитал доку и теперь до меня дошло.
Счетчик, о котором идёт речь - это счётчик ссылок на шаред блок. То счётчик, какой счётчик. Вот только кодеру надо писать это руками.
Только геморрой я написании такого кода. А я предлагаю как-то переложить эту работу на компилятор - если он стопудово уверен что ссылка всегда одна (объект в скопе), то должен просто мутировать немутабельное и подменить ссылку.
bormand 06.05.2014 23:40 # 0
Так точно.
> Вот только кодеру надо писать это руками.
Ну все-таки не самому кодеру, а автору либ типа Qt/boost/stl и т.п..
> Только геморрой я написании такого кода.
В том же Qt есть набор классов чтобы было легко мутить свои implicit shared класы - QSharedData и QSharedDataPointer. Порождаем от QSharedData класс, в который складываем все поля. Мутим внешний класс, в котором будут все методы, и одно поле - QSharedDataPointer<OurData> data. Вот и весь геморрой. Главно не забывать вызывать data.detach() во всех мутаторах.
> то должен просто мутировать немутабельное и подменить ссылку.
А откуда ему знать как мутировать какую-нибудь сложную структуру данных, если программист об этом не заботился, и писал код только для иммутабельного случая (с почленным копированием и всем таким)? В простых случаях может быть и разберется...
3.14159265 06.05.2014 23:47 # +1
Ну правильно. Тарасу.
>есть набор классов чтобы было легко мутить свои implicit shared класы - QSharedData и QSharedDataPointer. Порождаем от QSharedData класс, в который складываем все поля. Мутим внешний класс, в котором будут все методы, и одно поле - QSharedDataPointer<OurData> data. Вот и весь геморрой.
Ня!
3.14159265 06.05.2014 23:47 # 0
Тут джва основных шага:
а) clone()
б) mutate
Вот оно должно выполнить mutate на старой ссылке. Возможно это должно быть завернуто в некую абстракцию/соглашение - пиши так и оно оптимизнёт, к примеру копирующий конструктор или какой-то clone.
Компилеры они ж сука умные, вот видит в методе он копирующий конструктор, а следом небольшое изменение скопированного объекта.
И понимает (когда инлайнит этот метод в место вызова), что ссылка на локальный объект херится и приведет к вызову деструктора и free.
Он выпиливает копирующий конструктор, выпиливает деструктор старого, оставляет мутирование, но только подменяет ссылку чтоб мутировало старую структуру.
Или это бред?
bormand 06.05.2014 23:56 # +1
А вот на стеке он вполне справляется с выпиливанием лишних конструкторов копий (опять же, если они без побочных эффектов и не дрочат кучу).
3.14159265 07.05.2014 00:38 # 0
Заинлайненый скоп это ж и есть куча, или не?
То есть крестооптимизатор дуплит, что в таком коде:
ссылку на старый x выбрасываем и raii должно дернуть деструктор. при этом оно понимает что нам нужна копия только что выброшенного объекта, после копирующего конструктора в метода сразу идёт деструктор .
И в таком случае эти бесполезные действия сгорают - какой смысл уничтожать объект и перед этим делать его копию?
bormand 07.05.2014 05:31 # +1
А вот если конструктор или деструктор внутри делают new или delete - очень маловероятно, что он их выкинет. Все-таки для крестокомпилятора new и delete это черный ящик, о котором он ничего не знает.
3.14159265 07.05.2014 14:16 # 0
>> для крестокомпилятора new и delete это черный ящик, о котором он ничего не знает.
Потому что всё зависит от того как реализованы malloc/free?
roman-kashitsyn 07.05.2014 14:31 # +1
нет, потому, что new/delete можно переопределять.
3.14159265 07.05.2014 15:01 # 0
Так компилятор увидит что там напереопределял программист.
Не пойму что мешает сишко(кресто)компилятору понять, что вот есть malloc, вот есть memcpy в то место где выделили память, и вот есть free старого.
Если все три операции производятся с одинаковым размером очевидно что это тупое копирование.
Потому можно не делать malloc+memcpy+free, а вернуть старый указатель.
bormand 07.05.2014 15:23 # +1
Вообще не факт. Особенно если они где-нибудь в соседней дллке.
К тому же аллокаторы там очень сложные (борьба с фрагментацией кучи, потокобезопасность, быстрый поиск свободных блоков и т.п.), и заинлайнить он их всяко не сможет, даже если увидит. А не заинлайнив он не сможет применять к ним дальнейшие оптимизации.
Тут разве что помечать пары new/delete и malloc/free специальным атрибутом, как бы намекая компилятору, что он может творить с их вызовами непотребства...
3.14159265 07.05.2014 15:33 # 0
Так а это всё не сокрыто в malloce?
Да и неважно какой аллокатор, в той ситуации в которой я описал суть переложить из одного места в другое - независимо от внутренностей алокатора, разве что он не работает как-то неправильно.
Не могу представить себе ситуацию при нормально работающем алокаторе (без сайд-эффектов) где копирование буфера и немедленное удаление старого не эквивалентно реюзу оного.
Разве что пользовательские хуки отвалятся.
bormand 07.05.2014 15:37 # +2
Не факт. Там ведь хоть для каждого класса можно свои new/delete запилить.
> Не могу представить себе ситуацию при нормально работающем алокаторе (без сайд-эффектов) где копирование буфера и немедленное удаление старого не эквивалентно реюзу оного.
Да, тут я соглашусь. Но только если компилятор сумеет доказать, что аллокатор и деаллокатор работают над блоком одного размера.
3.14159265 07.05.2014 15:47 # 0
3ачастую это хождение по тонкому льду, да и часто ли нужно их переопредлеять находясь в здравом уме? Я к тому что пусть в кастомные new/delete такая оптимизация и не сможет, но это малая толика всего кода.
>только если компилятор сумеет доказать, что аллокатор и деаллокатор работают над блоком одного размера.
Само собой. Плюс должен копироваться такой же размер.
Хотя при разных размерах умный компилер может догадаться превратить malloc+memcpy+free в realloc.
PS Короче в хацкелл такое надо.
bormand 07.05.2014 15:51 # +1
Не-не-не девид блейн... Не забывай, что в вектора и мапы не только POD'ы пихают, но и сложные классы, которые нельзя просто так взять и передвинуть на другое место.
Да и реаллок почти всегда будет делать malloc+memcpy+free. В нем ведь адрес не меняется только если после предыдущего alloc'а не выделили что-то еще...
> Короче в хацкелл такое надо.
Возможно. Там язык более строгий, и структуры логически иммутабельны. Там может и взлететь.
3.14159265 07.05.2014 15:56 # 0
Лол. Ну да.
>Там язык более строгий
И функции маленькие и без сайд-эффектов, и компилер всё время что-то выводит и доказывает, и САМОЕ ГЛАВНОЕ - отсутствуют UB и new/ delete - не переопределяются.
bormand 07.05.2014 15:29 # +1
3.14159265 07.05.2014 15:42 # 0
bormand 07.05.2014 15:44 # 0
146%, что никак, ибо это для оптимизации объектов мутабельных ;)
После строчки v.push_back(std::move(row)); row станет пустым. Какая уж тут иммутабельность...
3.14159265 06.05.2014 21:39 # 0
tl;dr. Резюмируя пост выше - не ставить, а даже убирать fence, lock и guard, там где заведомо нету коров и прочего скота.
bormand 06.05.2014 22:53 # 0
3.14159265 06.05.2014 23:11 # 0
Слияние подряд идущих захватов локов одного монитора это другая фича lock coarsening.
>жабий jit же вроде умеет убирать лишние локи
Вроде умеет. Но эти фичи долгое время были эксперементальными, так что не факт.
В общем если объект не попадает на кучу (юзается в пределах метода), и мы синхронизируемся на нём, самый тупой случай: synchronized (new Object()), то теоретически должно выпилить - это lock elision.
Но я предлагаю применять похожую оптимизацию к немутабельным объектам, делая мутацию внутри, вместо копирования, в том случае если ссылка немедленно заменяется на новую копию.
3.14159265 06.05.2014 23:28 # 0
Так что надеюсь скоро и амд осилит.
guest 07.05.2014 18:18 # 0
brutushafens 07.05.2014 18:22 # −4
guest 07.05.2014 22:05 # −6
Си? Асмом? Лишпом? Какая разница чем?
hometwo 07.05.2014 22:32 # −4