Pull to refresh

Comments 66

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


Какое-то нездоровое желание
Аргументируйте Ваше высказывание, если не трудно, а то не совсем понятно, что именно вызывает у Вас неприятие такого (банального, в общем-то, по отношению к GC) пожелания.
Отличная статья! Спасибо за подробное описание процесса миграции и возникших проблем.
UFO landed and left these words here
Rob Pike на каком-то из выступлений (кажется, с GopherCon) говорил, что менять язык за исключением небольших правок они больше не планируют, считают его законченным. Упоминал, что вероятно когда-нибудь выйдет Go 2.0 и добавят generics туда.
Не холивара ради, но примерно год назад писали сервис, который должен был обрабатывать стабильно примерно 5к запросов, при этом кеш физически там никуда не присунешь. Для быстрого старта выбрали mongodb + node.js. И в итоге было 4 физ. сервера и 20 виртуалок Node.js. Нужно было двигаться дальше и смотрели в разные стороны. Смотрели на Go, он действительно очень быстр оказался для нашей задачи, примерно в 4-8 раза быстрее Node.js. В итоге уже год как работает Java (Netty, Morphia) + MongoDB и горя не знаем, хватает 1 физ. сервера и запас мощности 3-4 раза, не говоря уже о гораздо большем сообществе по сравнению с Go и наличие отличной IDE, профайлеров и так далее
А что именно перевесило в сторону Java по сравнению с Go, если не секрет? Подозреваю, что наличие знающего Java программиста )
Java учили на ходу, сам язык очень простой, вот все что вокруг него заставило потрудиться, чего одно Java EE стоит для не подготовленного человека. Но не так страшен черт, как его малюют.
1. Java реально быстрее
2. ООП и его прелести в больших проектах никто не отменял
3. Огромное количество готовых решений, велосипеды в прошлом, есть все
4. Наличие отличной IDE (автокомплит, дебаг, рефакторинг)

В итоге нет затрат времени на изобретение велосипедов, красивая объектная модель, легко поддерживать, тесты писать сказка (Spring, Mockito), есть поддержка всевозможных CI, Sonar. Если удручает сам язык — пишите на Scala, прекрасно вписывается в архитектуру
А каждого свой набор приоритетов )
Я тоже сначала думал над переписыванием на С или C++, но после знакомста с Go понял, что могу сэкономить себе кучу времени. В итоге получилось пусть немного менее производительное решение, зато нет утечек, возни с тредами да и итоговый код, думаю, сильно проще.
На C написать производительный HTTP Server тоже нужно суметь. А вот если познакомиться с репозиторием либ Java можно сэкономить еще больше времени
Это высоконагруженное веб-приложение, обрабатывающее… больше 10к запросов в секунду.

кластер на Go будет уже из шести [машин]…

обработать 10к… запросов за одну секунду на одной машине…

Так все-же на одной или на всем кластере?
Имелось ввиду, что запихнуть все на одну не просто. Обрабатываются на кластере, разумеется, запас должен быть всегда.
Понятно. Интересно было почитать, спасибо за пост!
(блок continue будет выполняться даже при раннем прерывании текущей итерации через оператор next). В Go для этого приходится использовать некошерный goto, который к тому же принуждает прописывать все декларации переменных перед ним, даже тех, которые не используются после метки перехода
Для этого можно использовать кошерный defer, кстати, не нужно городить «for ;;», конструкция «for {}» для этого существует.

Дальше смотреть не стал.
У Вас какая-то особенная версия Go? На моей работает вот так:
println("start for{}")
for i := 0; i < 3; i ++ {
        defer func(){ println(i) }()  
}
println("finish for{}")


результат:

start for{}
finish for{}
3
3
3
Не спорю, можно приспособить defer как это сделали Вы через обьявление тела цикла отдельной функцией, но стоит ли…

Лично мне жадно лишний раз на каждую итерацию делать дополнительные вызовы функций (defer хоть и вполне дешевый, но все же не бесплатный), да и назвать распухший код лучшим, чем использование goto — тоже вопрос.
defer и замыкание — очень не бесплатные, на самом деле.
groups.google.com/forum/#!topic/golang-ru/MlRvTOxWvig
Т.е. использовать такое внутри частых и длинных циклов — не очень хорошая идея.
Давид, я не увидел анонимной функции, лопух.
Вывод, конечно, из вашего примера.
Не понял?!

результат:
start for{}
0
1
2
finish for{}
Действительно, магия. Но работает у меня по другому и вывод, как у человека выше.
Нет никакой магии, в примере david_mz внутри цикла for обьявляется анонимная функция, defer запускается после каждой итерации, когда заканчивается выполняться эта функция.
Вы точно Гоу знаете?

    println("start for{}")
    for i := 0; i < 3; i ++ {
            defer func(i int){ println(i) }(i)  
    }
    println("finish for{}")
Не спешите писать комментарий, не проверив его.
Ваш пример вернет что-то типа:
start for{}
finish for{}
2
1
0

А теперь представьте ещё, что в цикле блок continue { } должен обработать несколько разных переменных — их все придется пробрасывать в defer с соответствующим оформлением аргументов — назвать такое решение кошерным могут только сильно ортодоксальные гоферы )
Я программировал и на Перле (пять лет) и на Гоу (три года), языки сильно разные, сравнивать их некорректно. Да, Гоу менее синтаксически насыщен, но это по нынешним временам скорее достоинство — язык можно быстро освоить и всё, что пишут другие программисты, даже профи, читается (что не сказать про Перл, многие ленятся учить его целиком, знаю поверхностно и могут прочитать далеко не всё).

Я не назову себя ортодоксальным гоуером, но Гоу люблю, да, не отнять. Вот вышеприведённая последовательность меня не смущает :) Ну переверну цикл и «finish» в суну в дефер выше уровнем :) Но если в цикле будет сотня итераций, я, конечно, поостерегусь использовать деферы и буду выкручиваться :)
Вышеприведенная последовательность могла бы Вас смутить хотя бы потому, что все defer-ы выполняются после полного завершения всех итераций цикла, что далеко от того, как работает Perl-овый continue {}.

Языка полностью разные, я их и не сравниваю напрямую, только те вещи, которые можно сравнить. Блок continue {} вполне мог бы присутствовать в Go в каком-то виде и он не сделал бы его хуже или сложнее, а вот удобнее в некоторых моментах — да.
Гоу, как я его называю, — Си с человеческим лицом. Его главное достоинство — простота. Из Перла можно брать очень многое, но тогда это Перл и получится. Согласен, continue — удобно, не спорю, но если начать добавлять туда всё удобное, не остановишься.

Авторы молодцы — сделали очень изящный и простой язык.

За ваш опыт и статью спасибо — любой практический опыт, да ещё и на русском, бесценен — сообщество пока невелико. Когда я начинал программировать на нём, даже биндинга ни к одной графической библиотеке не было, пришлось свой писать. Сейчас дела получше, но всё равно, по сравнению с сообществом других языков — страна и деревня по размерам :)
А Вы уверены, что блокировки Вам действительно необходимы? В Go есть прекрасный механизм передачи сообщений — оформляйте код, нуждающийся в блокировке в горутину и шлите ей сообщения.
Я специально отдельно рассмотрел этот вопрос в статье ). Каналы — это не серебрянная пуля, это всего лишь специфический инструмент, который в ряде случаев позволяет эффективно избавиться от использования блокировок в коде, но он вовсе не призван их убрать вовсе. Кроме того, реализация каналов внутри сама использует блокировки, просто предоставляя программисту более удобную (опять же, во многих, но не во всех случаях) абстракцию. Например, так как блокировка канала эксклюзивная, и функция-обработчик запросов тоже выполняется на одном ядре — при использовании каналов у Вас не будет возможности воспользоваться преимуществами shared RLock() блокировок, когда производительность не ограничиватся одним ядром.
В статье, кстати, есть пример, где используется мютекс для реализации функции crypt(). Представьте себе код, использующий каналы — он будет гораздо сложнее, а смысл?
Сами разработчики Go рекомендуют использовать каналы вместо блокировок Do not communicate by sharing memory; instead, share memory by communicating.

Есть подозрения что блокировки реализованные во внутренней кухне Go будут оптимальнее своих собственных.
http://golang.org/doc/effective_go.html#sharing
Do not communicate by sharing memory; instead, share memory by communicating.

This approach can be taken too far. Reference counts may be best done by putting a mutex around an integer variable, for instance. But as a high-level approach, using channels to control access makes it easier to write clear, correct programs.
Я уверен, что Вы в состоянии корректно перевести эту цитату, но все же позволю себе заметить, что она утверждает по сути то же, что и я: каналы есть зер гут, но не слишком фанатейте.

Вот другой пример: https://code.google.com/p/go-wiki/wiki/MutexOrChannel
So which should you use?

Use whichever is most expressive and/or most simple.

По моей практике, мьютексы применяются достаточно редко и только в каких-то местах, где уже надо отжать проценты производительности. Я согласен, что в примере с crypt применение мьютекса оправдано тем. что код получился проще, но он (пример) достаточно искусственный. Есть ведь функция crypt_r, лишённая этого недостатка, и никакие блокировки вообще не нужны. На реальных задачах код с использованием каналов получается более простым и читаемым.
Вы немного напутали, мютексы применяются в многопоточных приложениях весьма часто, и вовсе не в тех местах, где нужно отжать пару процентов производительности (в таких местах от мютексов наоборот пытаются избавиться), а в тех, которые могут одновременно работать с общими данными и нужно обеспечить последовательный (а не одновременный) доступ. В этом плане пример с crypt() совсем не искуственный, а как раз каноничный.

*_r аналоги есть не для всех функций, например для FreeBSD нет crypt_r(). Их реализация сложнее и, бывает, требует значительных дополнительных расходов cpu и памяти для работы, не всегда это востребовано.
Последовательный доступ можно сделать и на каналах, и на мьютексах. Я имел в виду, что на тех же задачах мьютексы иногда дают больше производительность, чем каналы. Чтобы не быть голословным:

for req := range ch {
  ...
  req.resp <- C.GoString(C.crypt(req.cryptStr, req.cryptSalt))[len(salt):]
  ...
}

Это не значит, что везде надо применять каналы. В данном случае понятно, что решение на каналах избыточно (создание и освобождение структур явно лишнее). Тут применение мьютекса оправдано. Применять надо то, что удобно в конкретной задаче. И обычно в моих задачах это оказываются каналы. Прошу прощения за неточность формулировок.
Интересно, как бы выглядил go, если бы в гугле решили воткнуть в него подобие ARC вместо GC.
В Go слишком много чего нужно было бы добавить, чтобы получился Rust. Но с другой стороны, уровня production-ready Go во многом из-за своей простоты (а также из-за того, что удалось переиспользовать компиляторы от Plan9) достиг гораздо быстрее. А в мире Rust до сих пор пытаются нащупать, каким должен быть идеальный язык.
Самое плохое в Go (имхо) — это обработка исключений по возвращаемым значениям.
Я бы назвал это не недостатком, а, скорее, особенностью языка. Исключения тоже не самый идеальный вариант, иначе они бы присутствовали в Go в полной мере. Авторы сделали выбор, который, по их мнению, лучше удовлетворяет поставленным перед ними задачам.
Я не пишу на Go, но так сложилось, что последние несколько лет приходится иметь дело с Java и Node.js. Так вот, в JavaScript исключения номинально тоже есть, но на деле обработка ошибок производится не через них, т.к. try ... catch не работает с асинхронным кодом. Вместо этого объект исключения передается в колбек первым параметром, и его наличие нужно проверять отдельно руками. По началу мне это тоже казалось чем-то жутко диким и неудобным, но со временем я понял, что с точки зрения того, сколько кода занимает обработка ошибок и сколько когнитивных усилий приходится на нее затрачивать, разницы вообще никакой нет. Думаю, с Go та же история.
В Go все проще, нежели на жутких колбеках JS.
Я не про сами коллбеки, а про обработку исключений: и там, и там нужно писать
if (error) { doErrorHandling(error); }
И и там, и там нужно про это вспомнить, и можно «проворонить» исключение, и нельзя «прогнать» обработку исключений куда-нибудь вниз большого try-catch блока, а вместо этого нужно писать логику того, что делать с каждой возможной ошибкой прямо в точке ее появления, т.д.

В общем, языки разные, но претензии со стороны тех, у кого в языках работающий try-catch, абсолютно одинаковые.
К слову, язык Rust тоже не поддерживает исключений, а факт ошибки определяется типом возвращаемого значения. Но Rust поддерживает макросы, и за счёт этого проброс ошибки наверх становится очень лаконичным:

my_fun(val) — так записывается вызов функции
my_macro!(val) — так записывается обращение к макросу.

fn do_smth() -> Result {
    let res1 = try!(do_sub1());
    let res2 = try!(do_sub2());
    return Ok(res1 + res2)
}

try! — это макрос, после разворота которого функция будет выглядеть так:
fn do_smth() -> Result {
    let res1 = match do_sub1() {
                  Ok(v) => v,
                  Err(e) => return Err(e)
               };
    let res2 = match do_sub2() {
                  Ok(v) => v,
                  Err(e) => return Err(e)
                };
    return Ok(res1 + res2)
}

Т.е. если do_subX() вернула ошибку, то она возвращается из функции с помощью return, а если вернулось нормальное значение — оно присваивается переменной для дальнейшего использования.
Rust вообще становится очень привлекательным языком. Чуваки ну вот просто все делают правильно: полноценный вывод типов, RAII, по умолчанию неизменяемые данные, unsafe-блоки, продвинутые макросы и т.д. В результате мы, если повезет, получим для наших программ корректность хаскеля и скорость выполнения плюсов.

Даже если раст в итоге не выстрелит, я думаю, что следующим системным языком-убийцей C++ будет что-то основанное на тех же идеях.
Ну, косяки там тоже есть, хоть я и надеюсь, что их поправят.

Например, сейчас если у вас есть функция, которая принимает параметром коллекцию и не меняет ее (например, считает что-то на основе данных в коллекции), то в Rust вам придется написать две версии такой функции: одну для immutable коллекции, и одну — для mutable, — потому что так сейчас устроена система типов. Конечно, код внутри можно переиспользовать, но через unsafe и прочие костыли.

Ну, «детские» болезни — обычный этап в развитии чего-то сложного. :-)

Кроме того, у ребят правильные общее направление (необходимость type inference, никаких нулевых указателей, etc.) и подход к делу: есть внятное и полное описание языка (пусть и часто меняющееся на первых порах), очень открытая разработка, активное обсуждение в рассылке и irc, дайджесты типа TWiR'а. То, что они сразу пишут параллельно Servo, и что компилятор достаточно рано стал self-hosted, — очень круто и позволяет надеяться, что язык получится как минимум жизнеспособным.
Например, сейчас если у вас есть функция, которая принимает параметром коллекцию и не меняет ее (например, считает что-то на основе данных в коллекции), то в Rust вам придется написать две версии такой функции: одну для immutable коллекции, и одну — для mutable, — потому что так сейчас устроена система типов.


Вы что-то путаете. В Rust мутабельность определяется не структурой самой по себе, а тем, что с ней можно сделать и в каком слоте она лежит.

Если вы создаёте, например, коллекцию, и хотите, чтобы кто-то мог её изменить, вы пишете методы, которые меняют коллекцию, так, чтобы они принимали &mut self. Методы, которые не меняют коллекцию, будут принимать просто &self. После этого вы можете вызывать изменяющие и неизменяющие методы, если структура лежит в мутабельной переменной, и только неизменяющие, если структура лежит в немутабельной переменной. Поэтому если вы не собираетесь менять коллекцию, вам понадобится только одна функция, которая принимает &; если же вы хотите менять коллекцию, в функцию вам понадобится передавать &mut:

pub struct SomeCollection<T> {
    // ...
}

impl<T: Copy> SomeCollection<T> {
    fn get(&self, i: uint) -> T { ... }
    fn set(&mut self, i: uint, v: T) { ... }
}

fn compute_something(coll: &SomeCollection<int>) -> int {
    // здесь можно вызвать coll.get():
    let x = coll.get(0);
    // но вызвать coll.set() нельзя, будет ошибка компиляции
    // coll.set(0, x+1); 
    x
}

fn compute_and_modify(coll: &mut SomeCollection<int>) -> int {
    // здесь можно и то, и другое
    let x = coll.get(0);
    coll.set(0, x+1);
    x
}

fn main() {
    // s1 - неизменяемый слот (переменная)
    let s1: SomeCollection<int> = SomeCollection { ... };

    // можно вызвать compute_something(), но не compute_and_modify()
    println!("{}", compute_something(&s1));
    // println!("{}", compute_and_modify(&mut s1));

    let mut s2 = s1;  // перемещаем в мутабельный слот
    // можно вызвать и то, и другое
    println!("{}", compute_something(&s2));
    println!("{}", compute_and_modify(&mut s2));
}

Я с Растом пока что на вы и очень может быть, что что-то напутал. Вот обсуждение проблемы на Реддите.

A common issue I've come upon in Rust is the following: you have some structure with a fairly complicated, or at least non-trivial, search procedure. The search procedure logically requires no mutation, and returns an immutable reference. However, you would also like to provide a variant that takes in a mutable version of the structure and returns a mutable reference. This does not affect the actual search procedure at all, it just changes the signature of the function (and maybe the signature of some temporary variables). However, the only way to do this in Rust at the moment is to duplicate the implementation.

The implementation of std::collections::TreeMap is a great example. find and find_mut are pretty much exactly the same logic, but the logic's been written out twice.
Это другое, чем то, что вы описали. Здесь мутабельность результата вычисления зависит от мутабельности исходной коллекции. И здесь да, придётся код повторять дважды, к сожалению, потому что в Rust нельзя параметризовать функции по мутабельности.

Однако этот юзкейс очень ограниченный. find и find_mut и аналогичные методы, возвращающие ссылки на внутренние элементы структуры данных — это чуть ли ни единственное, что страдает в этом случае. Более того, я бы сказал, что это специфично для обобщённых коллекций вроде того TreeMap; для собственных структур данных эта проблема будет возникать ещё реже.
Да, полностью согласен. Кстати, спасибо за пример!
А как на счет десктопного софта? Очень нужен язык стоящий между C# и Python. Важно удобство лепки GUI приложений средней руки…
> Вычисления моего кода просто теряются на фоне задач, которыми приходится заниматься runtime

Это вполне может быть из-за неоптимальности кода программы. Например, cgocall/System занимает много времени, тк вы делаете 6 cgo вызовов вместо 1 или 2.

> Ещё больше расстраивает невозможность проапгрейдить блокировку RWMutex — если блокировка в статусе RLock и обнаружилось, что необходимо внести изменения — извольте делать RUnlock(), затем Lock() и ещё раз проверять, есть ли все ещё неоходимость делать эти изменения или какая-то горутина уже всё успела сделать.

Это принципиально нельзя сделать. Если 2 горутины захватили RLock, и пытаются проапгрейдить его до Lock, они дедлочатся. Все, что тут можно сделать, это собственно и есть RUnlock и Lock. И, да, данные могут поменяться, но от этого никуда не деться.

> Также нет неблокирующей функции TryLock()

Если нужно делать только TryLock, то это легко решается с помощью atomic.Swap(&x, 0, 1). Если нужно делать и TryLock и Lock, то, да, это считается слишком сложным для мьютексов,

> Решение — запускать GC() почаще, для надежности лучше самостоятельно из программы.

Этого ни в коем случае не надо делать. Если нужно, что бы GC запускался чаще, то нужно ставить переменную окружения GOGC в значение меньше 100. Хотя в 1.3 на время паузы это не должно особо влиять (тк sweep фаза происходит одновременнно с работой приложения), только уменьшать объем памяти

Это вполне может быть из-за неоптимальности кода программы. Например, cgocall/System занимает много времени, тк вы делаете 6 cgo вызовов вместо 1 или 2.
Или наоборот, когда код настолько прост и быстр, что узким местом являются уже библиотеки и встроенные функции )
CGO присутствует, например для geoip, и тот же crypt(), но тут никуда не деться.

Ещё больше расстраивает невозможность проапгрейдить блокировку RWMutex
Это принципиально нельзя сделать.
На самом деле есть вариант без дедлока с апгрейдом через блокирующийся TryLock(), который либо проапгрейдит блокировку, либо вылетит, если кто-то ещё захочет это сделать до того, как остальные поснимают RLock(). Хотя, согласен, в общем случае лучше просто снять RLock и перелочиться.

Решение — запускать GC() почаще, для надежности лучше самостоятельно из программы.
Этого ни в коем случае не надо делать.
Пардон, но работать как-то нужно, а с паузами в несколько секунд это чертовски сложно (и дорого). Про настройку GOGC в курсе, но в доке не совсем внятно описано, как именно она работает, а экспериментировать не было времени — нужно было гарантированно работающее решение. Разброс времени паузы сейчас меньше 50%.
А воспользоваться чем-то типа «пула ресурсов» для целей облегчения жизни GC — не лучше? Или на архитектуру не ложится?
Пулы ресурсов хорошо ложатся на архитектуру, но они помогут от GC, только если создание нового объекта требует много аллокаций/деаллокаций делать.

Топикстартеру: вы много аллокаций делаете во время обработки каждого запроса? Например, если вы в цикле много-много раз делаете append к слайсу, то у вас часто будет реаллокация буфера. Если сразу отводить запас для роста побольше при помощи make, то можно прилично сэкономить. Если вы этого уже не делаете, конечно.
Запас я стараюсь выделять всегда. В процессе работы довольно много работы со строками — распарсить запрос, посмотреть в кеш, дернуть базу, если что… И ещё довольно много используется временных мап, без них тяжело.
> Пулы ресурсов хорошо ложатся на архитектуру, но они помогут от GC, только если создание нового объекта требует много аллокаций/деаллокаций делать.

Пулы ресурсов помогают уменьшить частоту сборок мусора независимо от количества аллокаций при создании объекта. Однако пулы не уменьшают время сборки.
В Go сборка мусора запускается, когда объем мусора в куче равен объему живых объектов. Если переиспользуются старые объекты, то количество мусора не растет.
В Go 1.3 для пуллинга ресурсов появился специальный компонент sync.Pool.
В моем случае вообще не ложится — GC тормозил на очистке мусора, остающегося после обработки каждого запроса. Пусть его и не сильно много на один запрос, но так как запросы идут с высокой частотой — мусора успевает накопиться прилично.
> CGO присутствует, например для geoip, и тот же crypt(), но тут никуда не деться.

От этого никуда не деться, но это не значит, что их нужно делать в неимоверных количествах. Вызовы можно агрегировать прямо в Go файле как:

/*
#include «somelib.h»
void myWrapper(...) {
… malloc(...);
someLibCall1(...);
someLibCall2(...);
free(...)
}
*/
import «C»

C.myWrapper(...)

> На самом деле есть вариант без дедлока с апгрейдом через блокирующийся TryLock()

Не вижу, как это решает проблему изменившихся данных. И как будет выглядеть код? Если TryUpgrade провалился, то что? Мы все равно делаем RUnlock и Lock? Выглядит бессмысленно

> Пардон, но работать как-то нужно

Не понимаю связи. Работать — надо. Вызывать runtime.GC не надо. Вместо этого нужно уменьшить GOGC, что приведет к увеличению частоты сборок.

Не вижу, как это решает проблему изменившихся данных. И как будет выглядеть код? Если TryUpgrade провалился, то что? Мы все равно делаем RUnlock и Lock? Выглядит бессмысленно
Согласен.

Не понимаю связи. Работать — надо. Вызывать runtime.GC не надо. Вместо этого нужно уменьшить GOGC, что приведет к увеличению частоты сборок.
А есть гарантии, что уменьшение GOGC уменьшит максимальное время работы GC и разброс длительности паузы? Из документации мне это не очевидно. Но я протестирую, просто пока не было возможности.
> А есть гарантии, что уменьшение GOGC уменьшит максимальное время работы GC и разброс длительности паузы? Из документации мне это не очевидно. Но я протестирую, просто пока не было возможности.

Нет, точно так же как и для ручного вызова runtime.GC.
В 1.3 обе эти вещи не должны особо влиять на время паузы. Пауза в основном зависит от объема живых данных в программе. Вот тут я описывал это более подробно:
software.intel.com/en-us/blogs/2014/05/10/debugging-performance-issues-in-go-programs
Пауза зависит не только объема живых данных, но и от объема мертвых данных, не так ли? В моем случае скорость накопления мертвых данных довольно высока, поэтому частый запуск GC позволяет не доводить время паузы до больших значений. Проблема с автоматической работой и тюнингом через GOGC в том, что даже при значении по умолчанию GC должен бы запускаться более-менее равномерно при более-менее равномерной работе, а этого не происходит (. Если при значении 10% вместо 100% GC будет запускаться в 10 раз чаще, то это одно, но если при этом время паузы будет так же продолжать отличаться почти в сотню раз на соседних запусках, то это все равно никуда не годится. Согласитесь — куда предпочтительней иметь настройку максимального времени паузы (желательно с учетом интервала запуска GC) с разумной настройкой по умолчанию, чем подбирать проценты соотношения свежевыделенной и занятой памяти.

Пока что не могу ничего сообщить о том, как влияет GOGC на длительность паузы в моем случае — время выхода на стабильный режим составляет много часов.
> Пауза зависит не только объема живых данных, но и от объема мертвых данных, не так ли?

Не так.
Смотрите ссылку выше — там все описано.

> GC должен бы запускаться более-менее равномерно при более-менее равномерной работе, а этого не происходит

Это действительно должно происходить. Не могу ничего сказать, т.к. у меня нет никаких данных.

> Согласитесь — куда предпочтительней иметь настройку максимального времени паузы

Не соглашусь.
Для non-concurrent mark&sweep GC такую гарантию нельзя обеспечить в общем случае. А там где можно, ее обеспечение может привести либо к чрезмерно большому потреблению памяти, либо к чрезмерно большому оверхеду на сборку мусора.
Мне почему то кажется, что на C++14 было бы не хуже и не труднее…
только вот в отличии от Python где есть yield, не красиво получается асинхронный код (а в синхронном я уже разочаровался).
Sign up to leave a comment.

Articles