LINUX.ORG.RU

Этот ваш reference counting

 ,


1

4

Во время попытки доказать одному товарищу, что Swift — ненужно, я наткнулся на интересную особенность:

import Foundation

let noLeak = 131028
let withLeak  = noLeak*10

class R {
    var _a : R?
    init(a : R?) {
        _a = a
    }
}

func test(n:Int, leak:Bool) {
    var p0 = R(a : nil)
    var p = R(a : p0)
    for _ in 1...n {
        p = R(a : p)
    }
    if leak {
        p0._a = p
    }
}

test(withLeak, true)
println("Evil leaking function")
test(noLeak, false)
println("Good function")

Первый вызов test просто течёт в лучших традициях Reference Counting, а вот второй падает со stack overflow (деаллокация, похоже, делается рекуррентно).

Интересно, есть ли такое же в Rust?



Последнее исправление: fmdw (всего исправлений: 1)
Ответ на: комментарий от Legioner

На мобильных платформах это критично. В принципе это легко проверяется — сравни, сколько памяти на флагманах Apple и на флагманах Samsung при сравнимом функционале и производительности.

Это ну очень спорное утверждение.

Недостаток оперативной памяти в Apple очень легко заметить, если запустить какую-нибудь тяжёлую игру или браузер с более чем двумя вкладками. А на Samsung всё продолжит летать.

В большей прожорливости Android виноват, скорее всего, не GC, а более сложный фреймворк и другая модель бэкграундных процессов.

Кроме того в OS X раньше был сборщик мусора. Сейчас его вроде убрали.

Его убрали не «сейчас», а ещё во времена первых моделей iPhone просто не запилили в iOS-версии Objective-C, когда оперативки было совсем мало, а потом для унификации подвели и десктоп под ARC.

Если бы решение принималось сейчас, то совсем не известно что бы выбрали, ARC или GC. Дело совсем не в плохой сочетаемости с Cocoa.

mono ★★★★★
()
Ответ на: комментарий от asaw

Для мертвых он должен как минимум вызвать finalize()

Тебе уже тут несколько раз говорили, что finalize есть у небольшого числа объектов и обрабатывается это отдельно, а не через обход всех мертвых объектов.

Плюс управлять памятью в реальной жизни

Для этого не нужны объекты.

anonymous
()
Ответ на: комментарий от tailgunner

Т.е. при идиоматичном подходе нельзя сделать список длинною более ~13k элементов, ARC с задачей не справляется (SIGILL!), и нужно писать итеративные деструкторы a la C/С++?

fmdw
() автор топика
Ответ на: комментарий от Legioner

В Rust все эти расходы во время выполнения программы нулевые за счёт усложнения языка и жизни программиста.

Нет, они не нулевые. Избавиться от счетчика ссылок нетривиальная задача. Есть теоретические разработки, связанные с выводом регионов и пр. Но в Rust это сделать не смогли. Там обычный подсчет ссылок в рантайме.

anonymous
()
Ответ на: комментарий от fmdw

в приведенном мной коде нет никакого подсчета ссылок, если что.

maloi ★★★★★
()
Ответ на: комментарий от fmdw

Т.е. при идиоматичном подходе нельзя сделать список длинною более ~13k элементов

Скорее можно сделать идиоматичный список, которому для удаления не хватит стека :)

ARC с задачей не справляется (SIGILL!)

Не понял, причем тут ARC, а SIGILL - скорее «просто баг».

и нужно писать итеративные деструкторы a la C/С++?

Сейчас - да. Если когда-нибудь сделают TCO - не нужно будет.

tailgunner ★★★★★
()
Ответ на: комментарий от mono

Это ну очень спорное утверждение.

Какое именно? То, что в GC-enabled приложениях требуется использовать в два раза больше памяти, чтобы производительность не проседала (читай, чтобы GC не дёргался безостановочно)? Это известный факт, могу найти пруф, если есть сомнения.

Недостаток оперативной памяти в Apple очень легко заметить, если запустить какую-нибудь тяжёлую игру или браузер с более чем двумя вкладками. А на Samsung всё продолжит летать.

Браузер не на Java написан, поэтому тут идёт игра на одном поле и бОльший объём оперативной памяти даёт безусловное преимущество.

В большей прожорливости Android виноват, скорее всего, не GC, а более сложный фреймворк и другая модель бэкграундных процессов.

Может быть и так. Но то, что GC жрёт больше, если не приседать специально (преаллокация всего на старте), это факт (в общем-то очевидный). Поэтому стандартное приложение на Java в андроиде будет требовать примерно в два раза больше памяти, чем такое же стандартное приложение на Objective C в iOS.

Про игры я ничего не скажу, там Unity в 99% случаев и как он устроен, я не совсем представляю. Скрипты там на C#, но сколько они памяти кушают в сравнение с нативной частью, я не знаю.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от fmdw

ARC с задачей не справляется

А это и не для него задача. Нерационально так использовать подсчет ссылок. Один счетчик на каждый элемент - это маразм.

и нужно писать итеративные деструкторы a la C/С++?

А что в этом плохого?

anonymous
()
Ответ на: комментарий от anonymous

Нет, они не нулевые. Избавиться от счетчика ссылок нетривиальная задача. Есть теоретические разработки, связанные с выводом регионов и пр. Но в Rust это сделать не смогли. Там обычный подсчет ссылок в рантайме.

Если ты используешь обёртку для подсчёта ссылок (Rc), то да, обычный подсчёт ссылок. Если ты её не используешь, то не будет подсчёта ссылок.

Legioner ★★★★★
()
Ответ на: комментарий от tailgunner

Просто для протокола: в примере нет счетчика ссылок.

Мы сейчас про какой код? В коде ТС нет счетчика ссылок?

anonymous
()
Ответ на: комментарий от Legioner

А, значит я не так понял тебя. Ок.

anonymous
()
Ответ на: комментарий от anonymous

Один счетчик на каждый элемент - это маразм.

Почему же? Может я tail sharing хочу.

А что в этом плохого?

Подтекающая программа чуть лучше, чем падающая. Так почему бы не писать на c++, используя смартпоинтеры когда нужно?

fmdw
() автор топика
Ответ на: комментарий от maloi

Если ты не в курсе, то у тех кто не переопределил finalize ничего не вызывается.

Это частный случай - раз. Памятью в реальной жизни приходится управлять по-настоящему, а не просто копировать всё каждый раз туда-сюда - два. Вот для этого сборщик мусора и должен знать какие именно объекты умерли, а какие - нет.

asaw ★★★★★
()
Ответ на: комментарий от asaw

Это частный случай - раз.

это 99-100% объектов в типичном приложении.

Памятью в реальной жизни приходится управлять по-настоящему, а не просто копировать всё каждый раз туда-сюда - два.

в реальной жизни в реальном hotspot при minor gc сборщик просто копирует туда-сюда.

Вот для этого сборщик мусора и должен знать какие именно объекты умерли, а какие - нет.

в реальной жизни в реальном hotspot сборщик мусора не знает ничего о мусоре, у которого нет finalize и на который нет phantom/weak/soft reference.

maloi ★★★★★
()
Ответ на: комментарий от fmdw

Подтекающая программа чуть лучше, чем падающая.

А с чего вдруг она подтекает?

Так почему бы не писать на c++, используя смартпоинтеры когда нужно?

Действительно, а почему?

anonymous
()
Ответ на: комментарий от asaw

Это частный случай - раз

«Частный случай» скорее наличие финализатора. Обычно-то его нет. Все объекты для вызова финализаторов не обходятся. Обходятся только те, у которых есть финализатор.

Памятью в реальной жизни приходится управлять по-настоящему, а не просто копировать всё каждый раз туда-сюда - два.

Тебе достаточно знать достижимые объекты. А все остальное - это просто память. Там уже нет никаких объектов, соответственно, нечего обходить.

anonymous
()
Ответ на: комментарий от fmdw

Так почему бы не писать на c++, используя смартпоинтеры когда нужно?

я не очень хорошо знаю c++, поэтому вопрос такой: будет ли аналогичный код на c++ со смартпоинтерами (или без, если можно) и без вручную написанного деаллокатора работать?

maloi ★★★★★
()
Ответ на: комментарий от anonymous

Тебе достаточно знать достижимые объекты. А все остальное - это просто память.

Ты как дятел долбишь одно и то же. «Просто память» может быть только если она упорядочена, а чтобы её упорядочить, нужно копировать, что очень дорого - дороже, чем управлять кучей.

asaw ★★★★★
()
Ответ на: комментарий от asaw

он появился в этом треде несколько часов назад как контр-пример твоему бреду, но ты видимо только что заметил.

maloi ★★★★★
()
Ответ на: комментарий от maloi

Если список делать в куче и без переопределённого деструктора, то работать будет, но с утечкой. В случае со смартпоинтерами — зависит от реализации.

fmdw
() автор топика
Ответ на: комментарий от anonymous

ну в первом сообщении код на swift или на одну строчку выше tailgunner привел ссылку на rust-овый код.

maloi ★★★★★
()
Ответ на: комментарий от fmdw

Если список делать в куче и без переопределённого деструктора, то работать будет, но с утечкой.

т.е. этот вариант сразу отметаем.

В случае со смартпоинтерами — зависит от реализации.

ну так вопрос в том, можно ли написать такую реализацию, чтобы не было stack overflow, или дело таки не в языках rust и swift, а в самой структуре данных, и на c++ будет все то же самое?

maloi ★★★★★
()
Ответ на: комментарий от maloi

т.е. этот вариант сразу отметаем.

Почему? Это же С++, где закат Солнца вручную — главная фича.

дело таки не в языках rust и swift, а в самой структуре данных

Со структурой данных всё в порядке:

Prelude> :m +Control.DeepSeq
Prelude Control.DeepSeq> deepseq [1..1000000] 1
Loading package array-0.5.0.0 ... linking ... done.
Loading package deepseq-1.3.0.2 ... linking ... done.
1
Prelude Control.DeepSeq> deepseq [1..100000000] 1
1
fmdw
() автор топика
Ответ на: комментарий от asaw

«Просто память» может быть только если она упорядочена, а чтобы её упорядочить, нужно копировать, что очень дорого - дороже, чем управлять кучей.

Зачем копировать мусор? Что ты несешь вообще?

anonymous
()
Ответ на: комментарий от fmdw

В случае со смартпоинтерами — зависит от реализации.

С чего вдруг тут взялась зависимость от реализации? Обычные стандартные смарты справятся.

anonymous
()
Ответ на: комментарий от asaw

Тебе написали, что все «объекты», которые нам недоступны(т.е. мусор), нас не интересуют как объекты, нам нужна только их память. И копировать ее не зачем - это мусор. Так что ты хотел копировать-то?

anonymous
()
Ответ на: комментарий от anonymous

У тебя «done» выводится в то же блоке, где объявлен p. Это ошибка. И да, при больших размерах списка сегфолтится.

fmdw
() автор топика
Ответ на: комментарий от anonymous

Просмотрел я, значит. Я так это модифицировал:

void test(size_t n)
{
    auto p = Nil();
    for (size_t i = 0; i < n; ++i) {
        auto x = std::move(p);
        p = Cons(1, std::move(x));
    }
    std::cout << "done? " << std::endl;
}

int main(int argc, char** argv)
{ 
    test(100000);
    std::cout << "done. " << std::endl;
}

Вот что на выходе:

$ g++ --std=c++14 test.cpp -o test && ./test
done? 
Segmentation fault

Те же грабли, скорее всего.

fmdw
() автор топика
Ответ на: комментарий от asaw

Какая связь между мной и этой ссылкой?

Очевидно же, что джавовский GC с финализаторами нанесли тебе непоправимую травму.

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

В .NET такая же финализация, и что с того?

asaw ★★★★★
()
Ответ на: комментарий от fmdw

И да, при больших размерах списка сегфолтится.

Ну значит там примерно та же проблема. И как тут уже говорили, лучше таки список очищать целиком, а не по узлам. В том смысле, что у всего списка один владелец, а не куча.

anonymous
()
Ответ на: комментарий от asaw

В len() рекурсия, ничего удивительного.

Он сегфолтится после вывода длины. Как раз на удалении.

anonymous
()
Ответ на: комментарий от fmdw

Ага, а если заменить

using List = const ListNode*;//std::unique_ptr<const ListNode>;

И написать clear(примерно так):

void clear(List p)
{
	while (p) {
		auto tmp = p;
		p = p->next;
		delete tmp;
	}
}

То вроде все ок...

В общем, как тут и говорили, нужен один владелец на весь список.

anonymous
()
Ответ на: комментарий от anonymous

В общем, как тут и говорили, нужен один владелец на весь список.

Н-но я хочу шарить хвосты как хаскеле...

fmdw
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.